Skip to content

Hydration happens after first render when navigating from a different page #280

@moneszarrugh

Description

@moneszarrugh

When I navigate to a page which I added the getStaticProps wrapper to, the hydration happens after an innitial render which results in my component getting rendered breifly without the new state changes made from the dispatch done in getStaticProps, then the hydration happens and the component rerenders with the new state.

Note that this issue only happens when I navigate from a different page. When I refresh from the page directly, the hydartion happens first, as it should, and then the component is rendered.

Sorry for lack of code snippets, will add if needed.

Edit: Code was added below.

I have create a simple counter app. The count starts at 0 but it is reset to 10 inside getStaticProps.

I have added console logs inside the HYDRATE function and the counter component

The console logs are used to indication when the hydration happens and when the counter app renders

The result of refreshing the http://localhost:3000/counter page is:

HYDRATE...
render...

However, the result of loading http://localhost:3000 and then navigating to http://localhost:3000/counter by clicking a Link is:

render...
HYDRATE...
render...

The problem is that there are 2 renders. One before hydration and another one after. The expected behaviour is one render AFTER hydration, same as above.

// store/counter.js

import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';

const slice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
  },
  reducers: {
    increment: (counter, action) => {
      counter.count += action.payload;
    },
    decrement: (counter, action) => {
      counter.count -= action.payload;
    },
    setCount: (counter, action) => {
      counter.count = action.payload;
    },
  },
  extraReducers: {
    [HYDRATE]: (counter, action) => {
      console.log('HYDRATE...');
      counter.count = action.payload.counter.count;
    },
  },
});

export default slice.reducer;

export const { increment, decrement, setCount } = slice.actions;

export const selectCount = state => state.counter.count;

// store/configureStore.js

import { configureStore } from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import counter from './counter';

export const wrapper = createWrapper(() =>
  configureStore({
    reducer: {
      counter,
    },
    devTools: true,
  })
);
// pages/_app.js

import '../styles/globals.css';

import { wrapper } from '../store/ configureStore';

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default wrapper.withRedux(MyApp);

// pages/index.js

import Head from 'next/head';
import Link from 'next/link';

import styles from '../styles/Home.module.css';

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Counter App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <div className={styles.title}>
          <Link href="/counter">Go to counter</Link>
        </div>
      </main>
    </div>
  );
}

// pages/counter.js

import Head from 'next/head';
import styles from '../styles/Home.module.css';

import { wrapper } from '../store/ configureStore';
import { useDispatch, useSelector } from 'react-redux';

import { selectCount, increment, decrement, setCount } from '../store/counter';

export default function Counter() {
  const dispatch = useDispatch();
  const count = useSelector(selectCount);
  console.log('render...');

  return (
    <div className={styles.container}>
      <Head>
        <title>Counter App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1>Counter App</h1>
        <h2>Count: {count}</h2>
        <button onClick={() => dispatch(increment(1))}>Increment</button>
        <button onClick={() => dispatch(decrement(1))}>Decrement</button>
      </main>
    </div>
  );
}

export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
  store.dispatch(setCount(10));
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions