Home > Enterprise >  Hydration error using styled-components and React Context
Hydration error using styled-components and React Context

Time:01-15

I have next error. I have two variants of theme in my app - dark & light.

ThemeContext.ts

export const ThemeContext = createContext<{
  theme: AppThemeInterface,
  setTheme: Dispatch<SetStateAction<AppThemeInterface>>,
  // eslint-disable-next-line no-unused-vars
  updateThemeKey: (newThemeKey: ThemeKeys) => void,
}>({
  theme: getTheme(ThemeKeys.DARK) // here is just object with props from interface,
  setTheme: () => null,
  updateThemeKey: () => null,
});

AppThemeProvider.ts Here I receive theme_key from LocalStorage and get theme by that key and set it value to ThemeContext

export const AppThemeProvider = ({ children }: AppThemeProviderProps) => {
  const [currentThemeKey, setCurrentThemeKey] = useLocalStorage<ThemeKeys>('theme_key', ThemeKeys.DARK);
  const [theme, setTheme] = useState<AppThemeInterface>(getTheme(currentThemeKey));

  const updateThemeKey = (value: ThemeKeys) => {
    setCurrentThemeKey(value);
  };

  return (
    <ThemeContext.Provider value={
      { theme, setTheme, updateThemeKey }
    }>
      <ThemeProvider theme={theme}>
        {children}
      </ThemeProvider>
    </ThemeContext.Provider>
  );
};

useLocalStorageHook

import { useState } from 'react';

export const useLocalStorage = <T>(key: string, initialValue: T) => {
  const [value, setValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    const lcItem = localStorage.getItem(key);
    const endVal = lcItem ? JSON.parse(lcItem) : initialValue;
    localStorage.setItem(key, JSON.stringify(endVal));
    return endVal;
  });

  const saveValue = (value: T): void => {
    localStorage.setItem(key, JSON.stringify(value));
  };

  const onChangeValue = (value: T): void => {
    saveValue(value);
    setValue(value);
  };

  return [value, onChangeValue] as const;
};

With such setup, when key in LocalStorage differ from dark, so in case in LocalStorage I have light i have next error

**

  • Prop className did not match. Server:

**

That is because first it's rendered as dark theme, but then it rerenders with light theme.

I don't understand how to make it render properly

CodePudding user response:

export const ThemeContext = createContext<{
  theme: AppThemeInterface,
  setTheme: Dispatch<SetStateAction<AppThemeInterface>>,
  // eslint-disable-next-line no-unused-vars
  updateThemeKey: (newThemeKey: ThemeKeys) => void,
}>({
  theme: getTheme(ThemeKeys.DARK) // here is just object with props from interface,
  setTheme: () => null,
  updateThemeKey: () => null,
});

CodePudding user response:

Problem

The state value will always be calculated on the server using the initialValue, however, when it's being calculated on the client it's using the key in localStorage (since window is defined on the client). For example, if the initialValue is "light" on the server, but may be "dark" in localStorage, you're going to get a hydration error because the value in state won't match.

Solution

Since localStorage is client-side only, you will need to calculate the theme value in a useEffect. In short, useEffect only runs on the client, therefore you'll always use the initialValue for both server and client, but on the client only, it'll be recalculated once the useEffect runs in the browser.

Demo

Here's a very simple demo that derives a theme based upon a value in localStorage.

Drawbacks

Using a client-side stored value will result in a UI flash if the value is anything other than the initialValue. You could avoid this flash by moving the value to an outside resource that can be accessed server-side. For example, saving this value to a database that can be accessed by using one of the Next lifecycle methods: getServerSideProps or getInitialProps. But this brings up another obstacle: How do you know which value in the database belongs to the current user? Regardless of whether a value is retrieved from the client or server, you're going to run into hurdles when it's used to dynamically set a global value.

  • Related