skip to Main Content

I have a list of elements where each element is a Game and as I toggle I dispatch an action to my slice where I want to affect the value of isDivOpen but just for the element (Game) I clicked on.

I created the following slice:

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

const initialState = {
  games: [],
  isLoading: false,
  isDivOpen: true,
};

export const gameSlice = createSlice({
  name: 'games',
  initialState,
  reducers: {
    toggleSeeMore(state, action) {
      const gameId = action.payload;
      console.log(gameId);
    },
  },
})

export const { toggleSeeMore } = gameSlice.actions;

export default gameSlice.reducer;

And the following is my component:

import React from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import '../../styles/components/Games/Game.css';
import { useSelector, useDispatch  } from 'react-redux';
import { toggleSeeMore } from '../../slices/gameSlice';

function Game({ game }) {
  const { name, description, employees, hours, photo } = game;
  const isDivOpen = useSelector(state => state.games.isDivOpen);
  
  const dispatch = useDispatch()

  const toggleMenu = () => {
    const gameId = game._id
    dispatch(toggleSeeMore(gameId))
  }

  return (
    <div className='game_card'>
      <div className="game_heading">
        <div className="image_container">
          <img src={`http://localhost:5000/uploads/${photo}`} alt="no photo" />
        </div>
        <h1>{name}</h1>
      </div>
      <div className="game_content">
        <p className="description">{description}</p>
        <div className="game_hours">
          {formattedTimes.map((time, index) => (
            <div key={index}>{time}</div>
          ))}
        </div>
      </div>
      <button onClick={toggleMenu}>See More<ExpandMoreIcon/></button>
      <div className={`employees ${isDivOpen ? 'show' : 'hide'}`}>
        <p>Employees</p>
        {employees.map((employee, index) => (
          <div key={index}>{employee}</div>
        ))}
      </div>
    </div>
  );
}

export default Game;

I am receiving the correct id of the game I’m clicking on, but I don’t know how to change the value of isDivOpen just for that game.

3

Answers


  1. This could be more simple than it is currently,

    Reducers could be helpful for large and complex state management but for the thing that show or hide is not really necessary

    You can delete the isDivOpen in redux state, then you can declare an useState for that in your component and hide or show based on that state… it will affect item individually

      const [showInfo, setShowInfo] = useState(true);
    
      // Another logic
    
      const toggleMenu = () => {
        setShowInfo(!showInfo)
      }
    

    Also in your JSX you can approach the show or hide like this (only another way to tackle this)

    {
      showInfo && (
        <div>
          <p>Employees</p>
          {employees.map((employee, index) => (
            <div key={index}>{employee}</div>
          ))}
        </div>
      );
    }
    

    That being said if you want to still show or hide each one using the reducer you has to basically change your structure and each game has to have the value related to if it is open or not

    Hope it helps

    Login or Signup to reply.
  2. You are on the right track. Instead of using isDivOpen as a boolean value though, you should store the passed gameId or null when deselecting the game. The UI will check the current openGameId value against its own game id value, and set the appropriate conditional value.

    Example:

    const initialState = {
      games: [],
      isLoading: false,
      openGameId: null,
    };
    
    
    export const gameSlice = createSlice({
      name: 'games',
      initialState,
      reducers: {
        toggleSeeMore(state, action) {
          const gameId = action.payload;
    
          // Toggles openGameId to gameId if not equal to current state, 
          // otherwise openGameId is toggled back to null to deselect
          state.openGameId = state.openGameId === gameId ? null : gameId;
        },
      },
      
    })
    
    function Game({ game }) {
      const dispatch = useDispatch();
      const { openGameId } = useSelector(state => state.games);
    
      const { _id: gameId, name, description, employees, hours, photo } = game;
    
      // Compute if this game is open
      const gameIsOpen = openGameId === gameId;
    
      const toggleMenu = () => {
        dispatch(toggleSeeMore(gameId));
      }
    
      return (
        <div className='game_card'>
          ...
          
          <button onClick={toggleMenu}>
            See More<ExpandMoreIcon />
          </button>
          <div className={`employees ${gameIsOpen ? 'show' : 'hide'}`}>
            ...
          </div>
        </div>
      );
    }
    
    export default Game;
    
    Login or Signup to reply.
  3. if you want to do it with the redux way ( not recommended ) then just update the reducer toggleSeeMore and add variable gameId to initial state.

    toggleSeeMore(state, action) {
       const id = action.payload;
       state.isDivOpen = state.gameId === id ? true : false
    },
    

    Recommended way, create a local state in the Game component,

    const [isDivOpen, setIsDivOpen] = useState(false)
    const toggleMenu = () => setIsDivOpen(!isDivOpen)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search