Search code examples
javascriptreactjsreduxnext.jsredux-toolkit

Redux toolkit store reset automatically in navigating between pages in next js


I'm a new Next user and have been using Redux with React for a long time I had a lot of trouble in using Redux with Next

I'm done with this solution

store.js

import { configureStore } from '@reduxjs/toolkit';
import reducers from './rootReducer';

export function makeStore() {
  return configureStore({
    reducer: reducers,
  });
}

const store = makeStore();

export default store;

rootReducer.js

import { combineReducers } from '@reduxjs/toolkit';
import tes from './test/tes';

const reducers = combineReducers({
  test: tes,
});

export default reducers;

_app.js

import React from 'react';
import { Provider } from 'react-redux';
import store from '../redux/store';
import { createWrapper } from 'next-redux-wrapper';

const MyApp = ({ Component, ...rest }) => {
  return (
    <Provider store={store}>
      <Component {...rest} />
    </Provider>
  );
};
const makestore = () => store;
const wrapper = createWrapper(makestore);

export default wrapper.withRedux(MyApp);

But I discovered that any use of the useDispatch Inside any page, the search engine does not recognize the content of the page after fetching the data

import React, { useEffect } from 'react';
import { Test } from '../../redux/test/tes';
import { useDispatch, useSelector } from 'react-redux';
import Link from 'next/link';

function TestPage() {
  const dispatch = useDispatch();
  const { data } = useSelector((state) => state.test);
  useEffect(() => {
    dispatch(Test('hi'));
  }, []);
  return (
    <div>
      <Link href="/">
        <a>home</a>
      </Link>{' '}
      {data.map((name) => (
        <h1>{name.title}</h1>
      ))}
    </div>
  );
}

export default TestPage;

One of the next pre-render methods must be used

I wonder if this is normal with next

or there Is a better way for doing that?


#1 Update

Now after moving data fetching to getStaticProps

TestPage.js

import React from 'react';
import { Test } from '../../redux/test/tes';
import {  useSelector } from 'react-redux';
import Link from 'next/link';
import { wrapper } from '../../redux/store';

function TestPage({ pageProps }) {
  const { data } = useSelector((state) => state.test);
  console.log(data);

  return (
    <div>
      <Link href="/">
        <a>home</a>
      </Link>{' '}
      {data && data.map((name) => (
        <h1>{name.name}</h1>
      ))}
    </div>
  );
}
export const getStaticProps = wrapper.getStaticProps(
  (store) => async (context) => {
    const loading = store.getState().test.loading;
    if (loading === 'idle') {
      await store.dispatch(Test('hi'));

    }

    return {
      props: {  },
    };
  }
);

export default TestPage;

The problem now is that the store is not updating useSelector return []

Although console.log (data) from getStaticProps the data is present __NEXT_REDUX_WRAPPER_HYDRATE__ i'm stuck


#2 Update

It was really hard to get here and after that, there are still problems getting Redux with Next js

Now everything works until navigating to any page have getStaticProps or getServerProps

state getting reset automatically

store.js

import reducers from './rootReducer';
import { configureStore } from '@reduxjs/toolkit';
import { createWrapper, HYDRATE } from 'next-redux-wrapper';

const reducer = (state, action) => {
  if (action.type === HYDRATE) {
    let nextState = {
      ...state,
      ...action.payload,
    };
    return nextState;
  } else {
    return reducers(state, action);
  }
};

const isDev = process.env.NODE_ENV === 'development';

const makeStore = (context) => {
  let middleware = [];

  const store = configureStore({
    reducer,
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware().concat(middleware),
    devTools: isDev,
    preloadedState: undefined,
  });

  return store;
};

export const wrapper = createWrapper(makeStore, { debug: isDev });

Solution

  • In the end, this way only worked. Even the server and Client state separation did not work.

    I used this jsondiffpatch.

    rootReducer.js

    const rootReducer = createReducer(
      combinedReducers(undefined, { type: '' }),
      (builder) => {
        builder
          .addCase(HYDRATE, (state, action) => {
            const stateDiff = diff(state, action.payload);
            const isdiff = stateDiff?.test?.data?.[0];
            const isdiff1 =
              stateDiff?.test1?.data?.[0] 
            return {
              ...state,
              ...action.payload,
    
              test: isdiff ? action.payload.test : state.test,
              test1: isdiff1 ? action.payload.test1 : state.test1,
            };
          })
          .addDefaultCase(combinedReducers);
      }
    );
    

    The only problem here is that you have to test every change in every piece inside the state

    Update

    Because a global hydrate reducer can be overkill, here is an example to handle hydration in each slice:

    import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
    import { diff } from 'jsondiffpatch';
    import { HYDRATE } from 'next-redux-wrapper';
    
    const initialState = {
      data: [],
    };
    export const TestFetch = createAsyncThunk(
      'TestFetch',
      async (data, { rejectWithValue, dispatch }) => {
        try {
          const response = await fetch(
            'https://jsonplaceholder.typicode.com/users'
          );
          const d = await response.json();
          return d;
        } catch (error) {
          return rejectWithValue(error.response.data.error);
        }
      }
    );
    
    const test = createSlice({
      name: 'test',
      initialState,
      reducers: {
        update: {
          reducer: (state, { payload }) => {
            return { ...state, data: payload };
          },
        },
      },
      extraReducers: {
        [HYDRATE]: (state, action) => {
          const stateDiff = diff(state, action.payload);
          const isdiff1 = stateDiff?.server?.[0]?.test?.data?.[0];
          // return {
          //   ...state,
          //   data: isdiff1 ? action.payload.server.test.data : state.data,
          // };
          state.data = isdiff1 ? action.payload.server.test.data : state.data;
        },
        [TestFetch.fulfilled]: (state, action) => {
          state.data = action.payload;
        },
      },
    });
    
    export const { update } = test.actions;
    export default test.reducer;