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 }
)