skip to Main Content

I have two instances of a component which I want them to behave like toggle buttons. If I click on a specific component instance, the other component instances should get deselected.

enter image description here

The below is my code:

store.js:

import { configureStore } from '@reduxjs/toolkit'
import boxReducer from './slices/boxSlice'

export default configureStore({
  reducer: {
    box:boxReducer
  }
})

boxSlice.js:

export const boxSlice = createSlice({
  name: "box",
  initialState: {
      selected: false,
  },
  reducers: {
    select: (state) => {
    state.selected=true;
    },
    deselect: (state) => {
        state.selected=false;
    },
  },
});

export const { select, deselect } = boxSlice.actions;
export default boxSlice.reducer;

Box.js:

export function Box(){
  const selectedStatus = useSelector((state) => state.box.selected)
  const dispatch = useDispatch()
  const [status, setStatus] = useState("Unselected")

  const onSelect = (e) => {
    dispatch(select());
    if (selectedStatus) {
      setStatus("Selected")
      dispatch(deselect());
    } else {
      setStatus("Unselected");
    }
  }

  return (
    <div tabIndex="0" onClick={onSelect} className="box">
      {status}
    </div>
  )
}

This is buggy. What I am observing is:

  • On first click the instance does not get updated. It gets updated only on second click
  • It does not toggle properly. Both remain selected or unselected

How do I update the other instances of the component properly, whenever one of the instances is clicked

3

Answers


  1. Instead of maintaining a separate state in your component, you can make use of the Redux state returned by the useSelector

    Try to click on the "Selected" and "Unselected" below to see it works:

    export function Box({ inverse }) {
      const selected = useSelector((state) => state.box.selected);
      const dispatch = useDispatch();
    
      const onSelect = () => {
        if (selected) {
          dispatch(deselect());
        } else {
          dispatch(select());
        }
      };
    
      const actualSelected = inverse ? !selected : selected;
    
      return (
        <div tabIndex="0" onClick={onSelect} className="box">
          {actualSelected ? 'Selected' : 'Unselected'}
        </div>
      );
    }
    
    <Box />
    <Box inverse />
    

    After that, you can have another boolean flag (eg. inverse) to know that which of the 2 box’s component should be inversed.

    const { useState } = React;
    const { Provider, useDispatch, useSelector } = ReactRedux;
    const { createSlice, configureStore } = RTK;
    
    const boxSlice = createSlice({
      name: 'box',
      initialState: {
        selectedId: 0
      },
      reducers: {
        select: (state, action) => {
          state.selectedId = action.payload;
        }
      }
    });
    
    const { select } = boxSlice.actions;
    const boxReducer = boxSlice.reducer;
    const store = configureStore({
      reducer: {
        box: boxReducer
      }
    });
    
    const Box = () => {
      // Random generate ID for this box. For React 18+, you can consider using useId() instead
      // Saving in state instead of memo ensures the value will always stay the same
      const [randomId] = useState(() => Math.random());
      const selectedId = useSelector((state) => state.box.selectedId);
      const isSelected = selectedId === randomId;
      const dispatch = useDispatch();
    
      const onSelect = () => {
        dispatch(select(randomId));
      };
    
      return (
        <div tabIndex="0" onClick={onSelect} className="box">
          {isSelected ? 'Selected' : 'Unselected'}
        </div>
      );
    };
    
    const App = () => (
      <Provider store={store}>
        <Box />
        <Box />
        <Box />
        <Box />
        <Box />
      </Provider>
    );
    
    ReactDOM.render(<App />, document.getElementById('react'));
    .box {
      padding: 16px 32px;
      border: 1px solid black;
      display: inline-block;
      font-family: sans-serif;
      margin-right: 16px;
      margin-bottom: 16px;
      user-select: none;
      cursor: pointer;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/redux.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/react-redux.min.js"></script>
    <script src="https://unpkg.com/@reduxjs/[email protected]/dist/redux-toolkit.umd.min.js"></script>
    <div id="react"></div>
    Login or Signup to reply.
  2. The redux state should be the source of truth. I suggest giving each box component a unique id and store the currently selected box id. If the same box id is already selected, deselect the box id, otherwise, set to the new id.

    Example:

    boxSlice.js

    import { createSlice } from "@reduxjs/toolkit";
    
    export const boxSlice = createSlice({
      name: "box",
      initialState: {
        selected: null
      },
      reducers: {
        select: (state, { payload }) => {
          state.selected = state.selected === payload ? null : payload;
        }
      }
    });
    
    export const { select } = boxSlice.actions;
    export default boxSlice.reducer;
    

    Box

    function Box({ id }) {
      const dispatch = useDispatch();
    
      const selected = useSelector((state) => state.box.selected);
      const isSelected = selected === id;
    
      const onSelect = () => {
        dispatch(select(id));
      };
    
      return (
        <div tabIndex="0" onClick={onSelect} className="box">
          {isSelected ? "Selected" : "Unselected"}
        </div>
      );
    }
    

    App

    <Box id={1} />
    <Box id={2} />
    

    Edit updating-state-of-different-instances-of-the-same-component-using-react-redux

    Login or Signup to reply.
  3. Try this

      export function Box(){
      const selectedStatus = useSelector((state) => state.box.selected)
      const dispatch = useDispatch()
      const [status, setStatus] = useState("Unselected")
    
      const onSelect = (e) => {
        dispatch(select());
        if (selectedStatus) {
          setStatus("Unselected")
          dispatch(deselect());
        } else {
          setStatus("Selected");
          dispatch(select());
        }
      }
    
      return (
        <div tabIndex="0" onClick={onSelect} className="box">
          {status}
        </div>
      )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search