Tips for effectively hydrating Redux state in Next.js upon page refresh?

I'm experiencing an issue with hydrating user data from local storage upon app reload or page refresh.

My project utilizes NextJS for the frontend, and for managing state across the application I rely on redux-toolkit and next-redux-wrapper.

Upon user login, the isLoggedIn boolean is stored in both local storage and the redux state. The appearance of the Navbar component is determined by the value of isLoggedIn, with the Navbar directly included in _app.tsx.

However, when a user refreshes any page, the isLoggedIn boolean isn't loaded into the state but remains in local storage.

In the past, I used redux-persist but encountered issues with UI rendering delays due to PersistGate waiting on persisted data retrieval, conflicting with the concept of SSR.

To address the loading problem with isLoggedIn, I currently utilize the App.getInitialProps method in _app.ts, triggering hydration through next-redux-persist on each page load. However, this implementation results in server-side rendering for all pages without benefiting from NextJS' static page optimization.

Is there a way to maintain NextJS' static page optimization, avoid using the redux-persist library, and successfully hydrate the client-side store upon page refresh?

Current code structure (some code omitted for brevity):

file: _app.tsx
import { wrapper } from 'store';

const MyApp = ({ Component, pageProps }: AppProps) => {
  return (
    <>
      <Navbar />
      <Component {...pageProps} />
    </>
  );
};


MyApp.getInitialProps = async (appContext) => {
  const appProps = await App.getInitialProps(appContext);

  return { ...appProps };
};

export default wrapper.withRedux(MyApp);
file: store.ts
import {
  combineReducers,
  configureStore,
  EnhancedStore,
  getDefaultMiddleware
} from '@reduxjs/toolkit';
import { createWrapper, MakeStore } from 'next-redux-wrapper';
import userReducer from 'lib/slices/userSlice';

const rootReducer = combineReducers({
  user: userReducer
});

const setupStore = (context): EnhancedStore => {
  const middleware = [...getDefaultMiddleware(), thunkMiddleware];

  if (process.env.NODE_ENV === 'development') {
    middleware.push(logger);
  }

  return configureStore({
    reducer: rootReducer,
    middleware,
    // preloadedState,
    devTools: process.env.NODE_ENV === 'development'
  });
};

const makeStore: MakeStore = (context) => setupStore(context);
export const wrapper = createWrapper(makeStore, {
  debug: process.env.NODE_ENV === 'development'
});
file: userSlice.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';

const initialState = {
  isLoggedIn: false
}

export const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    login: (state) => {
      state.isLoggedIn = true;
      localStorage.setItem('loggedInData', { isLoggedIn: true });
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(HYDRATE, (state, action: any) => {
        if (typeof window !== 'undefined') {
          const storedLoggedInData = localStorage.getItem('loggedInData');
          if (storedLoggedInData != null && storedLoggedInData) {
            const parsedJson = JSON.parse(storedLoggedInData);
            state.isLoggedIn = parsedJson.isLoggedIn ?? false;
          } else {
            state.isLoggedIn = false
          }
        }
      });
  }
});

export const isLoggedInSelector = (state: RootState) => state.user.isLoggedIn;

export default userSlice.reducer;
file: Navbar.tsx
import { useSelector } from 'react-redux';
import { isLoggedInSelector } from 'lib/slices/userSlice';

export default function Navbar() {
   const isLoggedIn = useSelector(isLoggedInSelector);
   return (
    <div className={`${ isLoggedIn ? 'logged-in-style' : 'logged-out-style'}`}>...</div>
   )
}

Answer №1

Encountered the same issue today. To address this, it's essential to separate client storage from page rendering and relocate it within useEffect when the component is mounted. The concept revolves around fully rendering the page first, then updating it with client storage data.

Incorporating direct client local storage can disrupt the hydration process.

Below is a code snippet that I implemented:

const MenuBar = () => {
  const isLoggedIn = useSelector((state) => state.isLoggedIn);
  useEffect(() => {
    // loads data from client's local storage
    const auth = loadAuthenticationToken(); 
    if (auth !== null) {
      // updates the store with local storage data
      dispatch(actions.jwtTokenUpdate(auth)); 
    }
  }, [dispatch]);
  
  if (isLoggedIn) {
    return <p>you are logged in</p>;
  } else {
    return <p>please log in</p>;
  }
}

Referencing a GitHub issue related to NextJS: https://github.com/vercel/next.js/discussions/17443

Also, here's a blog post where window access for rendering is necessary: https://dev.to/adrien/creating-a-custom-react-hook-to-get-the-window-s-dimensions-in-next-js-135k

Answer №2

The document mentions that incorporating getInitialProps in _app.js can lead to loss of static optimization. It is recommended to utilize redux solely on the client side, as opposed to also implementing it on the server side. Consequently, there may be no requirement for next-redux-wrapper since it inherently utilizes getInitialProps.

Refer to the example featuring redux-toolkit

Answer №3

It is important to understand that next-redux-wrapper leverages the getInitialProps method in app.js/ts when integrating with your store. This means that by using this library and wrapping your app with it, you may inadvertently sacrifice static optimization capabilities.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be causing the .env.development file to malfunction in my Next.js application?

I am currently working on Jest and testing library tests. Let's discuss a component named BenchmarksPage. Please pay attention to the first line in its return statement. import { Box, capitalize, Container, FormControl, InputLabel, MenuI ...

Encountering an issue while deploying a website with Firebase Auth as it fails and displays the error message "Unable to access 'auth' prior to initialization"

I'm currently working on a fitness website using Next.JS and Firebase. While everything is functioning flawlessly on my local host, I encounter build failures each time I try to deploy it using Firebase hosting. The error message reads: "Error occurre ...

Tips for utilizing array.items in joiful validation?

Can someone provide an example code or a link on how to correctly use the joyful validation for array items? I attempted the array.items validation code using joyful, but I am not sure how to specify the items. Thanks in advance! ...

Encountered an issue with Next.js error middleware where the response status is returning undefined

I have integrated next-connect into my next.js application for running server side code. I have implemented a custom error middleware, but it is throwing an error stating: res.status is undefined This is the implementation of my error middleware: export c ...

The conversion of an array to Ljava/lang/Object is not possible

I'm currently working on a project using NativeScript app with TypeScript where I am trying to pass an array of android.net.Uri to a function. However, when attempting to do so, I encounter an error mentioning that the 'create' property does ...

What could be causing this hydration error in NextJS in the development server build but not in the production build?

By using the create-next-app command and implementing this code snippet, a hydration error occurs on the client side when running the next dev server. The code in pages/index.js: export async function getServerSideProps(context) { const x = Math.random( ...

Accessing JSON data from a database using Angular

Wondering if there is a way to effectively access and manipulate JSON data stored in a database using Angular. While researching online, I keep coming across methods for reading JSON files from the Asset Folder, which does not align with what I need. What ...

Is it possible to circumvent making a double-API call using the getServerSideProps feature in NextJS?

Exploring the workings of NextJS' getServerSideProps, I've noticed an interesting pattern. Upon initially loading a page, the content is fully hydrated. However, upon navigating to a new page, an API call is triggered to fetch JSON data that will ...

React Context - Ensure synchronized deletion of user posts across routes while maintaining pagination during fetching

INTRODUCTION I am striving to replicate the behavior commonly seen in social networks, where deleting a post by a user results in its automatic removal across all app routes. This functionality is reminiscent of how platforms like Instagram or TikTok oper ...

Enhancing current interfaces

I'm exploring Koa and the module system in Node.js. Although I'm not asking about a specific koa question, all the code I'm working with involves using koa. In Koa, every request is defined by the Request interface: declare module "koa" { ...

Can you specify the necessary import statement for CallableContext?

My Google Cloud function is simple and looks like this: import * as functions from 'firebase-functions'; var util = require('util') export const repeat = functions.https.onCall( function (data, context) { console.log(&apo ...

The object might be undefined; TypeScript; Object

Why is it that the object may be undefined, even though it is hard-coded in my file as a constant that never changes? I've tried using ts-ignore without success. const expressConfig = { app: { PORT: 3000, standardResponse: `Server ...

React does not allow _id to be used as a unique key

When I retrieve the categories from my allProducts array fetched from the database using redux-toolkit, I filter and then slice the array for pagination before mapping over it. However, I keep encountering a warning: Each child in a list should have a un ...

Troubleshooting error in Angular 5 with QuillJS: "Parchment issue - Quill unable to

I've been working with the primeng editor and everything seems fine with the editor itself. However, I've spent the last two days struggling to extend a standard block for a custom tag. The official documentation suggests using the quilljs API fo ...

Guide to Re-rendering a component inside the +layout.svelte

Can you provide guidance on how to update a component in +layout.svelte whenever the userType changes? I would like to toggle between a login and logout state in my navbar, where the state is dependent on currentUserType. I have a store for currentUserTyp ...

Issues with sending emails through Nodemailer in a Next.js project using Typescript

I'm currently working on a personal project using Nodemailer along with Next.js and Typescript. This is my first time incorporating Nodemailer into my project, and I've encountered some issues while trying to make it work. I've been followin ...

The selected image should change its border color, while clicking on another image within the same div should deselect the previous image

https://i.sstatic.net/jp2VF.png I could really use some assistance! I've been working on Angular8 and I came across an image that shows how all the div elements are being selected when clicking on an image. Instead of just removing the border effect f ...

Refresh Material-Ui's Selection Options

Is there a way to properly re-render the <option> </option> inside a Material UI select component? My goal is to transfer data from one object array to another using the Material UI select feature. {transferData.map(data => ( <option ...

Ways to use DecimalPipe for rounding numbers in Angular - Up or down, your choice!

Looking to round a number up or down using DecimalPipe in Angular? By default, DecimalPipe rounds a number like this: Rounding({{value | number:'1.0-2'}}) 1.234 => 1.23 1.235 => 1.24 But what if you want to specifically round up or do ...

Leveraging the power of NextJS: Context API for seamless storage and transmission of

I'm currently working on a project with NextJS, aiming to create a shopping cart functionality using the useContext hook. My approach was fairly straightforward - I created a ShoppingCartContext, added an addToCart function to handle pushing data to t ...