Jordi Bassaganas
Jordi Bassaganas

Follow

Jordi Bassaganas

Follow
How to Share State Variables Between React Components

Photo by Lautaro Andreani on Unsplash

How to Share State Variables Between React Components

Stop writing the same thing over and over again

Jordi Bassaganas's photo
Jordi Bassaganas
·Sep 8, 2022·

5 min read

Play this article

When developing React apps, it's quite common to write reusable functional components which internal state will be shared by other Hooks. I had to do this recently while working on Redux Chess, a React Redux chessboard connected to a chess server.

Click here to view a demo.

I managed to share a state variable between multiple components as shown in the animations below.

figure-01.gif

figure-02.gif

figure-03.gif

As you can see, a dialog pops up when clicking on any of these buttons:

  • Play Online
  • Play a Friend
  • Play Computer

The thing is you don't want to reinvent the wheel and write the same color selection stuff over and over again. All three dialogs (parents) use the same functional component (child) to allow users select the color of the pieces.

figure-04.png

Here are the corresponding React files.

Each dialog has been implemented according to Redux Toolkit best practices. The MUI code is written in a file ending with the word Dialog while its state management logic is encoded in a slice.

It's two files for each dialog.

SelectColorButtons is a reusable, self-contained component meaning it doesn't need any slice because its state is internal. In other words, it doesn't actually make sense for it to be accessed by other components globally via a Redux store.

// src/features/dialog/SelectColorButtons.js

import * as React from 'react';
import { Avatar, ButtonGroup, IconButton } from '@mui/material';
import { makeStyles } from '@mui/styles';
import wKing from '../../assets/img/pieces/png/150/wKing.png';
import wbKing from '../../assets/img/pieces/png/150/wbKing.png';
import bKing from '../../assets/img/pieces/png/150/bKing.png';
import Pgn from '../../common/Pgn';

const useStyles = makeStyles({
  buttonGroup: {
    marginBottom: 10,
  },
  selected: {
    backgroundColor: '#d8d8d8',
  },
});

const SelectColorButtons = ({ props }) => {
  const classes = useStyles();
  const [color, setColor] = React.useState('rand');

  const handleSelectColor = (color) => {
    setColor(color);
  };

  React.useEffect(() => {
    props.color = color;
  }, [color]);

  return (
    <ButtonGroup className={classes.buttonGroup}>
      <IconButton
        aria-label="white"
        title="White"
        onClick={() => handleSelectColor(Pgn.symbol.WHITE)}
      >
        <Avatar
          src={wKing}
          sx={{ width: 55, height: 55 }}
          className={color === Pgn.symbol.WHITE ? classes.selected : null}
        />
      </IconButton>
      <IconButton
        aria-label="random"
        title="Random"
        onClick={() => handleSelectColor('rand')}
      >
        <Avatar
          src={wbKing}
          sx={{ width: 55, height: 55 }}
          className={color === 'rand' ? classes.selected : null}
        />
      </IconButton>
      <IconButton
        aria-label="black"
        title="Black"
        onClick={() => handleSelectColor(Pgn.symbol.BLACK)}
      >
        <Avatar
          src={bKing}
          sx={{ width: 55, height: 55 }}
          className={color === Pgn.symbol.BLACK ? classes.selected : null}
        />
      </IconButton>
    </ButtonGroup>
  );
}

export default SelectColorButtons;

SelectColorButtons basically receives the props from a parent component and changes its value whenever an icon button is clicked on.

const handleSelectColor = (color) => {
  setColor(color);
};

This is achieved by the handleSelectColor() function working along with React's useEffect Hook which listens for a state change in the color variable.

React.useEffect(() => {
  props.color = color;
}, [color]);

That's it!

Now let's have a look at the other way around.

The code of the parent components is perhaps a different story. A harsh truth nobody wants to hear about but it's worth saying is that full-stack web development may not be too obvious at times. Probably this is the case with Redux Chess provided that it communicates with both an API and a WebSocket server.

It is not the purpose of this post to go into the details of how Redux Chess works.

So I've removed the lines of code that deal with the WebSocket server and other stuff for the sake of simplicity to focus on what really matters. Remember, we want a state variable to be shared between components in a simple way.

Let's have a look at PlayComputerDialog as an example.

import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import CloseIcon from '@mui/icons-material/Close';
import {
  Button,
  Dialog,
  DialogContent,
  DialogTitle,
  Grid,
  IconButton,
  Slider,
  Typography
} from '@mui/material';
import Pgn from '../../common/Pgn';
import Dispatcher from '../../common/Dispatcher';
import * as mainButtons from '../../features/mainButtonsSlice';
import * as mode from '../../features/modeSlice';
import * as playComputerDialog from '../../features/dialog/playComputerDialogSlice';
import SelectColorButtons from '../../features/dialog/SelectColorButtons';
import WsAction from '../../ws/WsAction';

const PlayComputerDialog = () => {
  const state = useSelector(state => state);
  const dispatch = useDispatch();

  const [fields, setFields] = React.useState({
    level: 1,
    color: 'rand'
  });

  const handleCreateGame = () => {
    // The fields are now available for sending to the server
    // console.log(fields);
  };

  const handleLevelChange = (event: Event) => {
    setFields({
      ...fields,
      level: event.target.value
    });
  };

  // ...

  return (
    <Dialog open={state.playComputerDialog.open} maxWidth="xs" fullWidth={true}>
      <DialogTitle>
        <Grid container>
          <Grid item xs={11}>
            Play Computer
          </Grid>
          <Grid item xs={1}>
            <IconButton onClick={() => dispatch(playComputerDialog.close())}>
              <CloseIcon />
            </IconButton>
          </Grid>
        </Grid>
      </DialogTitle>
      <DialogContent>
        <Typography
          id="level"
          gutterBottom
          align="center"
        >
          Difficulty level
        </Typography>
        <Slider
          name="level"
          aria-label="Level"
          defaultValue={1}
          valueLabelDisplay="auto"
          step={1}
          min={0}
          max={3}
          marks
          onChange={handleLevelChange}
        />
        <Grid container justifyContent="center">
          <SelectColorButtons props={fields} />
        </Grid>
        <Button
          fullWidth
          variant="outlined"
          onClick={() => handleCreateGame()}
        >
          Create Game
        </Button>
      </DialogContent>
    </Dialog>
  );
}

export default PlayComputerDialog;

The useState Hook is used in PlayComputerDialog to manage the internal state of the dialog. The object variable fields holds the two values required to play the computer: level and color.

const [fields, setFields] = React.useState({
  level: 1,
  color: 'rand'
});

The dialog fields are passed to the child component as props.

<SelectColorButtons props={fields} />

Thus, the user can select a color in the child component while the parent gets automatically updated via the fields variable.

Conclusion

A simple way to share a state variable between a parent and a child component is to make sure that the former passes it to the latter as props.

In this example, we've seen how the fields of PlayComputerDialog are passed to SelectColorButtons.

props.color is then updated in the child component whenever its internal state changes. This is achieved by React's useEffect Hook which listens for a state change in the color variable.

Did you find this article valuable?

Support Jordi Bassaganas by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors
 
Share this