Search code examples
javascriptreactjsreduxreact-reduxredux-toolkit

Redux state object getting multiple renders and going undefined


Object called from Redux Re-rendering multiple times and becomes undefined. Please help me ?

I'm making a typing master tool using React and Redux.

""""" It gets called when a wrong input is given in the text input box """""

For that I created three slices as follows:

  1. Test-text -- To output the testing text
  2. Input-text -- To read the input text
  3. Result -- An object with three parameters { accuracy, WPM and WPMaverage }

When I'm fetching accuracy from the object it is re-rendering 4 times and going undefined please help

  1. 100
  2. 99
  3. 99
  4. undefined
  5. Nan

enter image description here

Please look at my code and let me know why is it happening

Sharing my github link :- https://github.com/vishwaTj/Typing_Master

I tried many things but I think it is due to the useEffect. The re-render used to match the input string is causing multiple state fetches I'm not sure.

Code of the slice for result:

import {createSlice} from "@reduxjs/toolkit"

const ResultSlice = createSlice({
    name:"Result",
    //REmember the { gap for curly brace "Oh my god"
    initialState: {
        Accuracy:100,
        WPM:40,
        WPMAverage:[]
    },
    reducers:{
        setAccuracy(state,action){
            return state.Accuracy-1;
        }
    }
})

export const ResultReducer = ResultSlice.reducer;
export const {setAccuracy} = ResultSlice.actions;
'''

code of slice for Input text

'''
import { createSlice } from "@reduxjs/toolkit";

const InputTextSlice = createSlice({
    name:"InputText",
    initialState:[],
    reducers:{
        setValue(State,action){
            return action.payload;
        }
    }
})

export const InputTextReducer = InputTextSlice.reducer;
export const {setValue} = InputTextSlice.actions;

code of the Store - index file

import { configureStore } from "@reduxjs/toolkit";
import {TestTextReducer} from './slices/TestText';
import { InputTextReducer } from "./slices/InputText";
import { ResultReducer } from "./slices/Result";
import { setTest} from "./slices/TestText";
import { setValue } from "./slices/InputText";
import { setAccuracy } from "./slices/Result";

const store  = configureStore({
    reducer:{
        TestText:TestTextReducer,
        InputText:InputTextReducer,
        Result:ResultReducer
    }
});

export {store, setTest, setValue, setAccuracy};

code of component which is rendering the state from redux

import React, { useEffect } from 'react';
import { useDispatch,useSelector } from 'react-redux';
import { setValue,setAccuracy } from '../store';

const TextBox = () => {
  
  const Test = useSelector((state)=>{
    return state.TestText;
  })

  const InputText = useSelector((state)=>{
    return state.InputText;
  })

  const Accuracy = useSelector((state)=>{
    console.log(state.Result);
    return  state.Result.Accuracy;
  })

  const dispatch = useDispatch();

  const handleChange=(e)=>{
      dispatch(setValue(e.target.value));
  }
 
  useEffect(()=>{
    handleMatch();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  },[handleChange])

  
 
  function handleMatch(){
    if(Test === InputText){
      console.log( "a cmoplete match");
        dispatch(setValue(""));
        return;
     } 
     if(Test.includes(InputText)){
      console.log("good going");
      return;
     }
     else{
      console.log(Accuracy);
      dispatch(setAccuracy());
      return;
     }
  }


  return (
    <div className='TextBox'>
        <h2>Lesson 1</h2>
        <input 
           type="text"
           className='text-input'
           value={Test}
           style={{backgroundColor:"rgba(55,90,127,255)"}}/>
        <input 
           type="Text" 
           className='text-input user' 
           onChange={handleChange}
           value={InputText}
           />
        <div className='performance'>
          <h4 className='Tags'>WPM:</h4>
          <h4 className='Tags'>Accuracy:</h4>
          <h4 className='Tags'>Average WPM:</h4>
        </div>
    </div>
  )
}

export default TextBox;

Solution

  • Based on the console log on TextBox.js:16, which it seems is logging the selected state.Result state that somewhere the state is mutated from an object to a number. Once this happens and an additional dispatch(setAccuracy()); is called, the setAccuracy reducer case attemps to subtract 1 from an undefined property of a number. Mathematical operations on non-numbers tend to generate exceptions at worst, and NaN at best.

    The setAccuracy reducer function is only returning state.Accuracy - 1. Redux-Toolkit reducer functions should either mutate the draft state or return the next state... all of it.

    Examples:

    const ResultSlice = createSlice({
      name: "Result",
      initialState: {
        Accuracy: 100,
        WPM: 40,
        WPMAverage: []
      },
      reducers: {
        setAccuracy(state) {
          state.Accuracy = state.Accuracy - 1;
        }
      }
    });
    

    or

    const ResultSlice = createSlice({
      name: "Result",
      initialState: {
        Accuracy: 100,
        WPM: 40,
        WPMAverage: []
      },
      reducers: {
        setAccuracy(state) {
          return {
            ...state,
            Accuracy: state.Accuracy - 1;
          };
        }
      }
    });
    

    The second method will work, but the first method is preferred since RTK implements immer.js under the hood and allows you to write succinct, mutable state updates. In other words, it allows you to write less code that is more clean and easier to read and maintain. See Writing Reducers with Immer.