Using React hooks like useState makes it easy to manage things like a shopping cart, but that state may only be available one instance at a time. What if we wanted that state to be available between multiple pages?
We can take advantage of React's Context API which lets us extend our cart state by making that state available anywhere we want in the app.
In our app, we want to move the cart from the Home page component to the header which requires us to use context. We will wrap our app in a CartContext.Provider
and pass in cart from our cart hook. This will allow us to grab subtotal
and checkout
for the Nav
component.
🚨There is no video covering the creation of the Nav.js file. Make sure to review the GitHub so you don't miss that.
Lecturer: [0:00] We're going to start off with our Space Jelly Shop Next.js App. We have three items that we're trying to sell. When we add them to our cart, we can see that reflected in our cart state. We also add a navigation to our application, where we have a static version of that same cart.
[0:13] Inside our code, we have a custom React hook called useCart that we use to maintain that state. We're importing it on our home page, so we can use it throughout our store. Finally, in our navigation, we have this button that has a static value of our cart.
[0:26] How can we take advantage of our cart hooks so that we can use that same value inside our navigation? React provides a context API that allows us to take data and pass it down from a top component all the way through the tree.
[0:37] A good example of that they provide is if we wanted to set a color scheme for our entire website that can change, we can pass that through from our context all the way down through the app.
[0:46] For our app, we're going to create a cartContext that we can use to wrap our entire store application. To start, we want to use the createContext function from React. We're going to add it to our import as createContext from React.
[0:59] Above our useCart hook, we're going to export a new constant called cartContext, which we're going to create from our createContext function. Now that we have our cartContext, we want to use it to wrap our application.
[1:11] Inside Next.js, they provide an app.js file which allows us to wrap our entire application. At the top of our app.js file, we're going to import our cartContext from a level up, hooks/useCart.js. Next, we're going to take our cartContext and we're going to replace these React fragments with cartContext.provider, similarly updating the closing tag.
[1:34] This provider property is a component that allows us to wrap our application passing data through the entire tree. Additionally, that provider component takes a prop called value. For now, we can just pass in an empty object. At this point, we can check our app and reload our page, but nothing should happen yet.
[1:48] Next, back inside of our useCart hook, we want to maintain two different hooks. First, we have our current useCart hook, which maintains our cartState. We also want to provide a hook where somebody can access the cartContext, which is going to allow us to pass that cartState all the way through our app.
[2:05] To start, I'm going to rename this useCart hook to useCartState. I'm also going to remove the default keyword. At the bottom of our useCart hook, I'm going to create a new hook called useCart. I'm going to export a new function called useCart similar to before, but this time, I'm going to create a constant called cart that I'm going to use to access the context.
[2:24] To do that, in our import statement at the top of the page, we also want to import the useContext hook where then, because we're defining cartContext in the same file, we can define this cart constant as useContext and then we can pass in cartContext. Finally, we can return that cart constant.
[2:41] To test this out, if you remember, we removed that default keyword from our original hook. We're now exporting useCart as a named export instead of the default export. In our import statement, we're going to change it to destructure our useCart hook from the file.
[2:57] If we go back to our app, we can see that we no longer have defined values. The reason is if we go back to our cartContext inside of app.js, we're currently only passing through an empty object. To fix this, inside our app.js file, I'm going to additionally import useCart state from our useCart hook file.
[3:15] At the top of our app component, I'm going to define a new constant called cart, which I'm going to set to our useCartState hook. Then I can pass in that cart value instead of the empty object into our provider. If we reload the page, we can now see our cart values. We can even test that this works by scrolling down, hitting Add to Cart, going back up and seeing our item.
[3:34] However, we're still not updating our navigation item. Inside of our nav.js file, we can import our useCart hook from two levels up, hooks/useCart.js. Now, we can destructure our subtotal value from our useCart hook. We can also replace our static value with our new subtotal variable.
[3:55] Now if we look at our app, we can see that our navigation has our cart value. If we go back down to our products and we add another item, we can see that now inside of our original cart, it updates as well as our navigation.
[4:06] If the goal is to eventually get rid of this original cart, we want to also be able to click this cart in order to go to checkout. Inside of our nav.js file, we can also import the checkOut function where we can add an event so when the button is clicked, we can fire that checkOut function.
[4:21] Now if we go back to our app and we click our cart, we get redirected to Stripe just like our original cart. To clean things up, because we have a global cart, we can now get rid of our original cart to tidy things up a bit.
[4:32] To review, we originally had a cart where if we added something to it, it would update right on our home page. We added a navigation and we wanted to make that cart stay globally available. To do that, we used the Context API from React and used createContext to create cartContext. We also used that cartContext to wrap around our application with a provider.
[4:51] We then updated the naming of our hook to useCartState to make it a little bit more clear, and added a new useCart hook to access our context where we passed in that cart value in through our context provider which allowed us to access our subtotal and our checkOut function from our global state to add our cart value to our global navigation.
Is there a performance worry about using usecontext with that data that will frequently change? Doesn’t updatingcart state trigger a rerender for all items?
hey! It might be a small hit but it should be negligible, especially compared to the benefits being able to manage that state globally
i feel like there spot missing between lessons 10 and 11 we jump from no nave to saying we implemented nav with no section covering that, just a heads up
i feel like there spot missing between lessons 10 and 11 we jump from no nave to saying we implemented nav with no section covering that, just a heads up
That's was intentional, this lesson was more about showing the concept vs adding the cart to the nav
Second, what happened on Nav? Very confusing, made me go back a lesson to see if I missed it.
When I remove the cart list I am returning the following error:
Unhandled Runtime Error
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of Home
.
Resolved the issue above. Removed a tag.
I am curious, from your code -- how were you able to import Nav from the file path you provided. Is the suffix (.js) hidden?
Returning Error As:
Unhandled Runtime Error
IntegrationError: Client-only Checkout does not support prices with usage_type=metered
in items[0]
.
When running:
<button onClick={checkout}>
<a>🛒 ${parseFloat(subtotal).toFixed(2)} </a>
</button>
Problem resolved:
Had to uncheck the 'used metered' option in the 'Product Pricing' section of Stripe.
Agreed that the Nav is confusing. Not sure why it was intentional to omit the whole creation of the components folder and structure.
The missing Nav made me confused too and I wasted time trying to find if I missed something.
I was a little upset that there are things that are just assumed like "We made this nav and you need to as well" before following along. What would be better is "before we go on, here is a challenge: Make this nav and then come back to this video". Makes the learner less confused and makes them solve a challenge as a practice.
Is there a performance worry about using usecontext with that data that will frequently change? Doesn’t updatingcart state trigger a rerender for all items?