简体   繁体   中英

ReduxToolkit useSelector Hook: React functional component doesnt rerender after selected redux-state is updated through useDispatch

Iam working on a Quiz-Game and i want the SingleChoice Component to fetch SingleChoice Questions from a QuizApi. When the user clicks on the start-button, to play the singleQuestionMode, the component should fetch the questions and display them on the screen (for testing purposes i just displayed a ,,hello" text instead). The start button should then disappear (after it was clicked).

To achieve that i created a redux-state called gameStarted, which is a boolean. I used useSelector() to import the state inside the component and to subscribe the component to state changes of that state. Inside the return statement i used the {}to inject a ternary operator which renders the button (if the game hasnt started yet aka gameStarted-state equals to false) and which renders the ,,hello" text and lets the button disappear if the user clicked on the button to start the SingleQuestionsMode (aka gameStarted has been set to true).

But if i click the start-button i can see in the devConsole of the browser, that the questions are fetched correctly and through redux-devtools i can see that the gameStarted redux state is correctly set to true (from false initially), but still the component doesnt rerender (doesnt display the "hello"-placeholder and the button doesnt disappear).

Screenshots from the dev-console in the browser: Before the Button is clicked: before button is clicked

After the Button has been clicked: After Button has been clicked

Why is that? Even if i initially set the gameStarted redux-state to true, then it displays the ,,hello"- placeholder text instead of the button. So it all seems to be setup correctly but something blocks the rerender after the gameStarted redux state is changed. Maybe redux-persist that i also used?

In the following is all of the relevant code:

SingleChoice.js Code:

import React, { useEffect, useState} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {selectGameStatus, setGameStarted} from "../loginSlice";

export default function SingleChoice() {
    const dispatch = useDispatch();
    const [questions, setQuestions] = useState([]);

    const gameStarted = useSelector(selectGameStatus);
    
    const fetchSingleQuestions = async () => {
        const questionData = await fetch('url-here');
        const questions = await questionData.json();
        setQuestions(questions.results)
        console.log(questions.results);
    }

    const startGame = () => {
        fetchSingleQuestions();
        dispatch(setGameStarted());
    }
     

    return (
        <div>
            <h1>SingleChoiceMode</h1>
            {!gameStarted ? <button onClick={startGame}>Spiel starten</button> : <div><h1>Hello</h1></div>}
        </div>
    )
}

Code of the slice with the above mentioned gameStarted state:

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


const initialState = {
    loggedIn: false,
    accountInfo: {
        id: "",
        username: "",
        mail: "", 
        password: "",
        singlescore: "", 
        multiscore: "", 
        mixedscore: ""
    },
    gameStarted: false
};
export const LoginSlice = createSlice({

    name: 'login',
    initialState,
    reducers: {
        setLoginTrue: (state) => {
            state.loggedIn = true;
        },
        setLoginFalse: (state) => {
            state.loggedIn = false;
        },
        setAccountInfo: (state, action) => {
            state.accountInfo = {
                id: action.payload.id,
                username: action.payload.username,
                mail: action.payload.mail,
                password: action.payload.password,
                singlescore: action.payload.singlescore,
                multiscore: action.payload.multiscore,
                mixedscore: action.payload.mixedscore
            }
        },
        setGameStarted: (state) => {
            state.gameStarted = true;
        },
        setGameStopped: (state) => {
            state.gameStarted = false;
        }
    }
});

export const selectLoginState = (state) => state.login.loggedIn;
export const selectAccountInfo = (state) => state.login.accountInfo;
export const selectGameStatus = (state) => state.gameStarted;
export const { setLoginTrue, setLoginFalse, setAccountInfo, setGameStarted, setGameStopped } = LoginSlice.actions;
export default LoginSlice.reducer;

Code of the redux-store (i also use redux-persist to keep the user loggedIn):

import { configureStore } from '@reduxjs/toolkit';
import loginReducer from '../features/loginSlice';
import storage from "redux-persist/lib/storage";
import {combineReducers} from "redux"; 
import { persistReducer } from 'redux-persist'

const reducers = combineReducers({
  login: loginReducer
})

const persistConfig = {
  key: 'root',
  storage
};

const persistedReducer = persistReducer(persistConfig, reducers);


const store = configureStore({
  reducer: persistedReducer,
  devTools: process.env.NODE_ENV !== 'production'
});

export default store;

Code of the Index.js:

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <PersistGate loading={null} persistor={persistor}>
          <App />
        </PersistGate>
      </Provider>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);

Code of the component App.js where everything is routed and rendered

import React from 'react';
import Home from "./features/Home";
import SingleChoice from "./features/modes/SingleChoice"
import MultipleChoice from "./features/modes/MultipleChoice"
import Mixed from "./features/modes/Mixed"
import Login from "./features/Login"
import Profile from "./features/Profile"
import Rankings from "./features/Rankings"
import NotFound from "./features/NotFound"
import Register from "./features/Register"
import Protected from "./features/Protected"
import { NavBar } from "./features/Navbar";
import './App.css';
import { Routes, Route } from "react-router-dom";

function App() {

  return (
    <div className="App">
      <NavBar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route element={<Protected />}>
          <Route path="/single" element={<SingleChoice />} />
          <Route path="/multiple" element={<MultipleChoice />} />
          <Route path="/mixed" element={<Mixed />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/rankings" element={<Rankings />} />
        </Route>
        <Route path="/login" element={<Login />} />
        <Route path="/register" element={<Register />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

Please help me, so that the SingleChoice component finally rerenders after imported redux-state has changed.

In SingleChoice.js , instead of

const gameStarted = useSelector(selectGameStatus);

    ...

const startGame = () => {
    fetchSingleQuestions();
    dispatch(setGameStarted());
}

type :

const { gameStarted } = useSelector((state) => state.login);

...

const startGame = () => {
    fetchSingleQuestions();
    dispatch(setGameStarted(true));
}

In loginSlice.js replace

setGameStarted: (state) => {
    state.gameStarted = true;
},

by :

setGameStarted: (state, action) => {
    state.gameStarted = action.payload;
},

demo : Stackblitz

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM