skip to Main Content

I’m stating to learn react and redux and I cannot figure out why my react component does not re-render after redux store state change.

Here is my code:

const initialState = { value: 'Loading...' }

function onSetName(state = initialState, action) {
  if (action.type === 'doc/docnamechanged') {
    return {
      ...state,
      value: action.payload
    }
  }
  return state
}

export const setName = text => {
  return {
    type: 'doc/docnamechanged',
    payload: text
  }
}

export const store = configureStore({ reducer: onSetName })
store.subscribe(() => Layout(), App());

export default function App() {  
  return (
    <Routes>
      <Route element={<Layout />}>
        <Route path="/1" element={<PublicPage />} />
      </Route>
    </Routes>
  )
}

thats my dispatch

  if (store.getState().value != "AD") {
    store.dispatch(setName("AD"));
  }
function Layout() {
  console.log(store.getState().value);

  const name = store.getState().value;

  return (
    <div className="App">
      <div className="header">
        <img className="logo" src="images/logotype.png" alt="logo" />
        <div className="UpperTitle">
          {name}
        </div>
      </div>
    </div>
  )
}

So I can see in console that store.getState().value does change, but in rendered <div className="UpperTitle"> {name} does not change. As you can see I tried to subscribe my App() function as well, but that did not help at all. Appreciate any help.

I manage to make it somehow work if I subscibe render() in index.js, but that seemed wrong and cause a warning message

react-dom.development.js:86 Warning: Render methods should be a pure function of props and state; triggering nested component updates from render is not allowed. If necessary, trigger nested updates in componentDidUpdate.

2

Answers


  1. Calling store.getState in a component doesn’t subscribe it to any changes, it simply gets the state once and that’s it. Something else would need to occur to trigger the Layout component to rerender so store.getState would be called again.

    This isn’t how we subscribe to the store in React components though. You can use the connect Higher Order Component, or the more modern useSelector hook.

    import { useSelector } from 'react-redux';
    import { Outlet } from 'react-router-dom';
    
    function Layout() {
      const name = useSelector(state => state.value); // <-- subscribe to changes
      
      useEffect(() => {
        console.log({ name });
      }, [name]);
    
      return (
        <div className="App">
          <div className="header">
            <img className="logo" src="images/logotype.png" alt="logo" />
            <div className="UpperTitle">
              {name}
            </div>
          </div>
          <Outlet /> // <-- for the nested routes 😉
        </div>
      );
    }
    

    You will need to ensure the Redux store is being provided to the React app.

    import { Provider } from 'react-redux';
    import { configureStore } from '@reduxjs/toolkit';
    
    const initialState = {
      value: 'Loading...'
    };
    
    function onSetName(state = initialState, action) {
      if (action.type === 'doc/docnamechanged') {
        return {
          ...state,
          value: action.payload
        };
      }
      return state;
    }
    
    export const setName = text => ({
      type: 'doc/docnamechanged',
      payload: text,
    });
    
    export const store = configureStore({
      reducer: onSetName,
    });
    
    export default function App() {  
      return (
        <Provider store={store}>
          <Routes>
            <Route element={<Layout />}>
              <Route path="/1" element={<PublicPage />} />
            </Route>
          </Routes>
        </Provider>
      );
    }
    

    Since it appears you are actually already using Redux-Toolkit, you should really use the current Redux patterns.

    import { Provider } from 'react-redux';
    import { configureStore, createslice } from '@reduxjs/toolkit';
    
    const initialState = {
      value: 'Loading...'
    };
    
    const nameSlice = createSlice({
      name: "name",
      initialState,
      reducers: {
        setName: (state, action) => {
          state.value = action.payload;
        },
      },
    });
    
    export const {
      ...nameSlice.actions,
    };
    
    export const store = configureStore({
      reducer: nameSlice.reducer,
    });
    
    import { setName } from '../path/to/name.slice';
    
    ...
    
    dispatch(setName("AD"));
    
    Login or Signup to reply.
  2. for rerender an component you need call a hook or change a state inside it!

    also Layout is not a pure function, and you can’t call it in subscibe,

    so remove this line from your code :

    //store.subscribe(() => Layout(), App());
    

    for rerender on store’s state change you need to change component’s state in subscibe:

    first we create a state like below in Layout Component :

    const [name, setNameState] = useState(store.getState().value);
    

    then I add a useEffect hook to Layout and use subscribe method inside it :

    useEffect(() => {
        store.subscribe(() => {
          console.log(store.getState().value);
          setNameState(store.getState().value);
        });
      }, []);
    

    When the redux state value changes, the subscribe will called, after that the name state changes in Layout and component will be rerender.

    you can see demo of my answer

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search