How to use useContext and Cookies together correctly from server?

57 Views Asked by At

For context, I am able to use useContext and js-cookies to render/save the changes in client-side, but since I am not able to access the cookies from the server-side loading in the stateContext.js, the initial values saved to the state are always the empty ones. This causes a hydration error every time I refresh the page.

As a way to crudely fix this, in getServerSideProps I get the state cookies from the req in context (which I believe i don't have access to it in stateContext.js) and put it as a prop. Then I check if the state obtained from useContext has the empty state, and change it if so. Still, I do feel like I'm missing something with this implementation. Thanks

_app.js

import { StateProvider } from '../utils/cart/stateContext'

const MyApp = (props) => {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

  return (
    <CacheProvider value={emotionCache}>
      <ThemeProvider theme={themeMode}>
        <CssBaseline />
        <StateProvider>
          <Component {...pageProps}/>
        </StateProvider>
      </ThemeProvider>
    </CacheProvider>
  );
};

stateContext.js

import React, { createContext, useEffect, useReducer } from "react";
import Cookies from 'js-cookie'

export const StateContext = createContext();

const emptyState = {
    cart: {
        cartItems:[]
    },
    initial: true
}

function reducer(state, action) {
    state.initial = false
    // removed some irrelevant code
    return state;
}

export const StateProvider = ({ children }) => {
    const initialState = Cookies.get('state') ?  JSON.parse(Cookies.get('state')) : emptyState
    const [state, dispatch] = useReducer(reducer, initialState);
    
    useEffect(() => {
        Cookies.set("state", JSON.stringify(state));
    }, [state]);

    return (
        <StateContext.Provider value={{ state, dispatch }}>
            {children}
        </StateContext.Provider>
    );
};

index.js


import { StateContext } from '../utils/cart/stateContext';
const db = require('../models/index.js');
const Products = db.products;

export async function getServerSideProps(context) {
  const products = await Products.findAll();
  const stateServ = JSON.parse(context.req.cookies.state ?? "{}");

  return {
    props: {
      products: JSON.parse(JSON.stringify(products)),
      stateServ
    }
  }
}

export default function Home({products, stateServ}) {
  let { state } = useContext(StateContext)
  if (state.initial && Object.keys(stateServ).length) {
        state = stateServ;
  }

  return (
    <Layout state={state}>
      <div>
        <h1>Products</h1>
      </div>
    </Layout>
  )
}

Layout.js uses it in the navbar like so

<Link>{state.cart.cartItems.length > 0 ? (<Badge color="secondary" badgeContent={state.cart.cartItems.length}>Cart</Badge>) : ('Cart')}</Link>

EDIT: As pointed out by @juliomalves, importing Layout with { ssr: false } is enough to remove the hydration error, so using getServerSideProps to send the state is unnecessary:

const Layout = dynamic(
    () => import('../components/Layout'),
    { ssr: false }
)
0

There are 0 best solutions below