react-use-cart
react-use-cart copied to clipboard
Next.js cart storage
Hi!
I'm using the library with Next.js.
When trying to using the library in pages which rendered server-side, the content of the server doesn't match the client. It seems this could be related to the cart being stored using localStorage and this is not available on the server, only in the client.
One thought it to use the storage prop, have you ran into this issue before ?
I tried using the storage prop and the following snipped fails with the following error. Could you provide an example of how the prop should be used and its initial value
function MyApp({ Component, pageProps }) {
const [cart, setCart] = useState();
return (
<div>
<CartProvider storage={() => [cart, setCart]}>
<Layout>
<Component {...pageProps} />;
</Layout>
</CartProvider>
</div>
);
}

I've seen this before, but I haven't figured out a solution yet.
Very interested in this!
The might be a solution in this thread: https://www.reddit.com/r/learnreactjs/comments/emtl9c/having_trouble_with_state_and_using_localstorage/
Haven't tried it yet.
@debatz I was thinking we could do something like adding:
useEffect(() => setMounted(true), []);
Inside of the CartProvider. It's a cheap way of checking if all is mounted, and if so, we can continue.
I haven't used this library yet, but was looking at it, and had found the same error using Uppy with NextJS.
My solution was to wrap the Uppy component with this:
ClientOnly.tsx
import dynamic from "next/dynamic"
import React from "react"
const ClientOnly = props => (
<React.Fragment>{props.children}</React.Fragment>
)
export default dynamic(() => Promise.resolve(ClientOnly), {
ssr: false
})
Your approach seems cleaner, adding just for context @notrab
Hey, has there been a solution for this yet in NextJS? I'm getting a server and client mismatch on page reload.
@notrab That approach is working for us.
import { Fragment, useEffect, useState } from "react";
import { CartProvider as BaseCartProvider } from "react-use-cart";
export default function CartProvider({ children, ...props }) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true));
if (mounted) {
return <BaseCartProvider {...props}>{children}</BaseCartProvider>;
}
return <Fragment>{children}</Fragment>;
}
I;m trying to use @joshuabaker 's solution but getting an error calling setCartMetadata inside an useEffect hook. Any ideas on how to fix this?
TypeError: setCartMetadata is not a function```
@smarquez1 do you have the provider set higher than where you're using that hook?
@notrab yes, I uploaded a project to github with a minimal version of my main project. Here are the important bits:
https://github.com/smarquez1/cart-test/blob/main/components/CartProvider.tsx https://github.com/smarquez1/cart-test/blob/main/pages/_app.tsx https://github.com/smarquez1/cart-test/blob/main/pages/index.tsx
and it's failing here and anywhere I call a useCart() hook
This is happening because the initialState doesn’t include placeholder methods for the various setters.
https://github.com/notrab/react-use-cart/blob/f3f5d0216769ed54cdd1d98ec4ead2f6529dd30d/src/index.tsx#L55-L62
Until we mount we only have access to the values defined here, which don’t include noops.
Here’s another hacky workaround…
const noop = () => {};
const { updateCartMetadata = noop } = useCart();
@notrab If you made use of this open-source hook you might save the hassle on the mounting stuff.
I used the solution above (https://github.com/notrab/react-use-cart/issues/88#issuecomment-1086117615) for a while, i.e. conditionally rendering CartProvider only once the app is mounted.
However that leads to a couple of issues
- it causes a re-render of all all components below
MyCartProviderafter react hydrates the page - i.e. a re-render of just about the whole app - sometimes trying to use
inCart(),isEmptyetc. elsewhere in the app will fail, asCartProvideris not in place
So I think it's better to conditionally render the components/data which depend on the state of the cart instead.
Contrived example based on new react docs:
// _app.jsx
import { CartProvider } from 'react-use-cart'
function Application({ Component, pageProps }) {
return (
<CartProvider>
<Component {...pageProps} />
</CartProvider>
)
}
export default Application;
// MyComponent.jsx
import { useCart } from "react-use-cart";
import { useEffect, useState } from "react";
export default function MyComponent() {
const { totalUniqueItems } = useCart();
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
if (isClient){
return (
<p>You have {totalUniqueItems} items in the cart</p>
)
}
else {
return (
// stuff which will only render on the server
)
}
}
i have this same problem, found another package instead that works with nextjs
https://github.com/mcnaveen/cart
If someone would like to help merge into support for App Router and other React contexts, please let me know. I don't have so much time these days to work on this as I've no sponsorship for this package currently.
@notrab I am using the package inside App Router and seems to be working with no problems. Do you know of any issues?
https://github.com/mcnaveen/cart
That package lacks a lot of features such as updateItemQuantity, and haven't been updated in a while.