Search code examples
javascriptreactjsnext.jsreduxredux-toolkit

Is there a way to use localStorage in initialState of Redux Toolkit in Next.js 14?


I am trying to set the initial value of a Redux Toolkit slice for the dark mode using the localStorage, however, for Next.js the window is not defined on the server-side, causing an error.

Usually, the solution is using the if (typeof window !== "undefined"), but for the initialState it's not completely correct since a value must be always given even if the window is not defined and you don't know if that value will be correct. I know lazy initializers exist, however I need an initial value from the beginning of the loading process of the page, since I need to set a darkMode prop to the root HTML element based on that value.

This is my code for the dark mode slice:

"use client";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

export interface DarkModeState {
    value: boolean;
};

const initialState: DarkModeState = {
    value: window.localStorage.getItem("darkMode")?.toString() === "true"; //! Server error
};

export const darkModeSlice = createSlice({
    name: "darkMode",
    initialState,
    reducers: {
        setDarkMode: (state, action: PayloadAction<boolean>) => {
            state.value = action.payload;
        },
    },
});

export const { setDarkMode } = darkModeSlice.actions;

export default darkModeSlice.reducer;

And this is the root element where I set the darkMode prop:

"use client";
import { useAppSelector } from "@/store/hooks";
import React, { ReactNode, useEffect } from "react";

export default function AppWrapper({ children }: { children: ReactNode }) {
    const darkMode = useAppSelector((state) => state.darkMode.value);
    useEffect(() => {}); // just to make sure it's a client component
    return (
        <div id="app" data-theme-app={darkMode ? "dark" : "light"}>
            {children}
        </div>
    );
};

I already tried using the useEffect hook to match the possibly incorrect dark mode state's random initial value of the server with the one on the localStorage of the client, however, this leads to an ugly switch of theme during every loading of the page.

Is there a correct way to correctly initialize that dark mode state right from the start of loading process, and so that there are no mismatch problems and hydration issues between server and client?


Solution

  • I was only able to solve this problem using the next-themes package, without Redux Toolkit. This package does everything I need, even avoiding the flickering when loading the page. About the hydration issues, they should expected since the server doesn't know the user's preference beforehand and there is nothing we can do, so, as the package recommends, suppressHydrationWarning should be used where necessary.

    I think there are no workarounds to solve the problem with plain Redux Toolkit other than dynamically loading the whole page with the dynamic() function of next/dynamic and disabling SSR with the option { ssr: false }, which however is not ideal in Next.js.