next.jsreact-reduxredux-toolkitnext-redux-wrapper

next-redux-wrapper HYDRATION failed


I am trying to integrate next-redux-wrapper to Next.js RTK project. When invoking async action from getServerSideProps, I am getting state mismatch error (see the image below).

When I dispatch action from client side (increment/decrement), everything works well. I think issue is related to HYDRATION but so far all my efforts have failed.

I tried mapping redux state to props, storing props in component state, added if statements to check values but nothing seem to work. I've been stuck on this for 2 weeks. I'm not sure what else to try next.

store/store.js

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import counterReducer from "./slices/counterSlice";
import { createWrapper, HYDRATE } from "next-redux-wrapper";

const combinedReducer = combineReducers({
  counter: counterReducer,
});

const reducer = (state, action) => {
  if (action.type === HYDRATE) {
    const nextState = {
      ...state, // use previous state
      ...action.payload, // apply delta from hydration
    };
    return nextState;
  } else {
    return combinedReducer(state, action);
  }
};

export const makeStore = () =>
  configureStore({
    reducer,
  });

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

store/slices/counterSlice.js

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

const initialState = {
  value: 0,
  data: { quote: "" },
  pending: false,
  error: false,
};

export const getKanyeQuote = createAsyncThunk(
  "counter/kanyeQuote",
  async () => {
    const respons = await axios.get("https://api.kanye.rest/");
    return respons.data;
  }
);

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(getKanyeQuote.pending, (state) => {
        state.pending = true;
      })
      .addCase(getKanyeQuote.fulfilled, (state, { payload }) => {
        state.pending = false;
        state.data = payload;
      })
      .addCase(getKanyeQuote.rejected, (state) => {
        state.pending = false;
        state.error = true;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

pages/index.js

import React, { useState } from "react";
import { useSelector, useDispatch, connect } from "react-redux";
import {
  decrement,
  increment,
  getKanyeQuote,
} from "../store/slices/counterSlice";
import { wrapper } from "../store/store";

function Home({ data }) {
  const count = useSelector((state) => state.counter.value);
  // const { data, pending, error } = useSelector((state) => state.counter);
  const dispatch = useDispatch();
  const [quote, setQuote] = useState(data.quote);
  return (
    <div style={{ display: "flex", flexDirection: "column" }}>
      {/* <span>{pending && <p>Loading...</p>}</span>
      <span>{error && <p>Oops, something went wrong</p>}</span> */}
      <div>{quote}</div>
      <span>Count: {count}</span>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  );
}

export const getServerSideProps = wrapper.getServerSideProps(
  (store) =>
    async ({ req, res, ...etc }) => {
      console.log(
        "2. Page.getServerSideProps uses the store to dispatch things"
      );
      await store.dispatch(getKanyeQuote());
    }
);

function mapStateToProps(state) {
  return {
    data: state.counter.data,
  };
}

export default connect(mapStateToProps)(Home);

Errors in console enter image description here


Solution

  • This might stem from a known issue where next-redux-wrapper 8 hydrates too late. Please try downgrading to version 7 for now and see if that resolves the problem.