Search code examples
javascriptreactjsreact-reduxredux-toolkit

Reducer not updating the state properly


rootReducer.js

import { combineReducers } from "redux";
import counterReducer from "./counter.reducer";

const rootReducer = combineReducers({
    counter: counterReducer
})

export default rootReducer

counterReducer.js

import { DECREMENT_COUNTER, INCREMENT_COUNTER } from "../Constants";
import initialState from "../initialState";

const counterReducer = (state = initialState, action) => {
    switch (action.type) {
        case INCREMENT_COUNTER: {
            return {
                ...state,
                counter: state.counter + 1
            }
        }
        
        case DECREMENT_COUNTER:
            return {
                ...state,
                counter: state.counter - 1
            }

        default:
            return state
    }
}

export default counterReducer

App.js

import React from 'react';
import './App.css';
import { useDispatch, useSelector } from 'react-redux';
import { decrementCounter, incrementCounter } from './actions/counter.action';

function App() {
  const selector = useSelector(state => state)
  console.log(selector)
  const dispatch = useDispatch()
  return (
    <div>
      <h1>Counter: {selector.counter}</h1>
      <button onClick={() => dispatch(incrementCounter())}>Increment</button>
      <button onClick={() => dispatch(decrementCounter())}>Decrement</button>
    </div>
  );
}

export default App;

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

reportWebVitals();

store.js

import { configureStore } from "@reduxjs/toolkit";
import initialState from "./initialState";
import rootReducer from "./reducers/root.reducer";

const store = configureStore({
    reducer: rootReducer,
    preloadedState: initialState
})

export default store

initialState.js

export const initialState = {
    counter: 0
}

So I'm trying to implement counter with increment and decrement button, but when I click on increment or decrement button it throughs me error stating

Objects are not valid as a React child (found: object with keys {counter}).

I've tried console logging the state and I found that initially my state looks like

counter: 0

But when I click on the increment or decrement button state becomes

counter: {
  counter: NaN
}

I'm not able to figure out why this is happening.


Solution

  • Issue

    If the state is initially counter: 0 then I suspect the initialState of the counterReducer is actually just something like const initialState = 0;. It would seem then the problem is your counterReducer function is injecting a nested counter property.

    state.counter is undefined, and when you add/subtract it results in a NaN, and then all further math operations on NaN are NaN.

    const state = 0;
    
    console.log({ state, "state.counter + 1": state.counter + 1 });

    Once state.counter is mutated with the nested property, it becomes state.counter.counter. It's this counter.counter object that can't be rendered as JSX.

    Solution

    Update counterReducer increment/decrement cases to add to or subtract from just state which is the counter value.

    const counterReducer = (state = initialState, action) => {
      switch (action.type) {
        case INCREMENT_COUNTER:
          return state + 1;
            
        case DECREMENT_COUNTER:
          return state - 1;
    
        default:
          return state;
      }
    }
    

    In the UI you'll want to select the specific state you want to subscribe to. Don't write selector functions that return the entire state, e.g. don't use useSelector(state => state);.

    function App() {
      const counter = useSelector(state => state.counter);
      const dispatch = useDispatch();
    
      return (
        <div>
          <h1>Counter: {counter}</h1>
          <button onClick={() => dispatch(incrementCounter())}>
            Increment
          </button>
          <button onClick={() => dispatch(decrementCounter())}>
            Decrement
          </button>
        </div>
      );
    }