Home > Back-end >  ReactJS not re-rendering after context changed
ReactJS not re-rendering after context changed

Time:03-20

I have a basic app here and I'm trying to config React Context properly but it isn't working. My goal is to render PlayScreen with the content of currentStage inside the React Context. Game changes the context but App keeps rendering PlayScreen with the "welcome" string, instead of "won" or "lost".

Also, I know that gameContext.js is for autocompletion but I added "welcome" there to have a first default state. Somehow I couldn't find a way to set up that very first "welcome" context when App is rendered for the first time.

I tried feeding PlayScreen with the context itself and didn't work, and now I tried setting a state with it but it doesn't work either (even when using useEffect and having the context as a dependency).

So I have two questions, what am I doing wrong? and, my way to set up the "welcome" default state is wrong? If so, how can I do it? Thanks.

gameContext.js

import React from 'react';

const GameContext = React.createContext({
  currentStage: 'welcome',
  playerStage: (stage) => {},
});

export default GameContext;

GameProvider.jsx

import React, { useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import GameContext from './gameContext';

const defaultState = {
  currentStage: '',
};

const gameReducer = (state, action) => {
  if (action.type === 'STAGE') {
    return {
      currentStage: action.playerStage,
    };
  }

  return defaultState;
};

const GameProvider = ({ children }) => {
  const [gameState, dispatchGameAction] = useReducer(gameReducer, defaultState);

  const playerStageHandler = (playerStage) => {
    dispatchGameAction({
      type: 'STAGE',
      playerStage,
    });
  };

  const gameContext = useMemo(
    () => ({
      currentStage: gameState.currentStage,
      playerStage: playerStageHandler,
    }),
    [gameState.currentStage]
  );

  return (
    <GameContext.Provider value={gameContext}>{children}</GameContext.Provider>
  );
};

GameProvider.propTypes = {
  children: PropTypes.node.isRequired,
};

export default GameProvider;

App.jsx

import React, { useContext, useState, useEffect } from 'react';
import GameProvider from './store/GameProvider';
import GameContext from './store/gameContext';
import PlayScreen from './components/PlayScreen';
import Settings from './components/Settings';
import Game from './components/Game';

const App = () => {
  const gameContext = useContext(GameContext);
  const [stage, setStage] = useState(gameContext.currentStage);

  useEffect(() => {
    setStage(gameContext.currentStage);
  }, [gameContext]);

  const [currentScreen, setCurrentScreen] = useState({
    playScreen: true,
    settings: false,
    game: false,
  });

  const changeScreenHandler = (newScreen) => {
    switch (newScreen) {
      case 'playScreen':
        setCurrentScreen({
          playScreen: true,
          settings: false,
          game: false,
        });
        break;
      case 'settings':
        setCurrentScreen({
          playScreen: false,
          settings: true,
          game: false,
        });
        break;
      case 'game':
        setCurrentScreen({
          playScreen: false,
          settings: false,
          game: true,
        });
        break;
      default:
        break;
    }
  };

  return (
    <GameProvider>
      {currentScreen.playScreen && (
        <PlayScreen stage={stage} onChangeScreen={changeScreenHandler} />
      )}
      {currentScreen.settings && (
        <Settings onChangeScreen={changeScreenHandler} />
      )}
      {currentScreen.game && <Game onChangeScreen={changeScreenHandler} />}
    </GameProvider>
  );
};

export default App;

PlayScreen.jsx

import PropTypes from 'prop-types';

const PlayScreen = ({ stage, onChangeScreen }) => {
  const clickHandler = () => {
    onChangeScreen('settings');
  };

  return (
    <div>
      <h1>{stage}</h1>
      <button type="button" onClick={clickHandler}>
        Go
      </button>
    </div>
  );
};

PlayScreen.propTypes = {
  stage: PropTypes.string.isRequired,
  onChangeScreen: PropTypes.func.isRequired,
};

export default PlayScreen;

Game.jsx

import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import GameContext from '../store/gameContext';

const Game = ({ onChangeScreen }) => {
  const gameContext = useContext(GameContext);

  const wonHandler = () => {
    onChangeScreen('playScreen');
    gameContext.playerStage('won');
  };

  const lostHandler = () => {
    onChangeScreen('playScreen');
    gameContext.playerStage('lost');
  };

  return (
    <div>
      <h1>GAME RUNNING</h1>
      <button type="button" onClick={wonHandler}>
        won
      </button>
      <button type="button" onClick={lostHandler}>
        lost
      </button>
    </div>
  );
};

Game.propTypes = {
  onChangeScreen: PropTypes.func.isRequired,
};

export default Game;

CodePudding user response:

You are consuming the GameContext above the GameProvider. The context shouldn't be available because App isn't being provided the context by the GameProvider.

Try moving everything underneath GameProvider into its own component and consume the context there.

  • Related