The Case for Shared State
Okay then my friends, so in this lesson I want to make some state which is going to keep track of any products that we add to the shopping cart. Now on each product page, I'm going to add a little button and when we click on that button, that's when we want to add that product to the cart. The way that we'll do that is by making a store to keep track of all of those products that we've added to the cart.
So the store value will be essentially just an array of product objects, and every time we click on a button to add a new product to the cart, a new object is going to get added to the store. But we need to think a little bit about how this is going to work, because we're going to need to access this store on various pages. We're going to need it on the product page so that we can add new products to the store, but also on the cart page so that we can access all the products in the store and list them out. Also in the header, we'll eventually be showing how many items are in the cart as well. So we need to access the store inside the header.
So then, if we need to access the store in multiple pages and components in the application, where's the best place to make the store? Well, there's two options. We could just make a store in a new file, export it, and then just use it in all the different locations we need it in, because stores and signals don't have to be tied to specific components. So we could do that, but instead, I want to take this opportunity to talk about context in SolidJS, because a context and a context provider can allow different pages and components to share state.
Conceptual Overview of SolidJS Context
So the way this works is very similar to how it works in React applications. We make a new component, and inside that component, we make whatever state it is that we want to share between other components. In our case, that would be the store which holds an array of products in the cart. So this component at the minute is just like any other Solid component that contains a store inside it.
Next, we make a new context object using one of Solid's built-in functions. And on that context object, we have access to a special provider component which is used to provide the context to other components. So inside our own custom component we made, we return this context provider component in the template, and then we wrap that around props.children. And that means that when this component that we just made wraps any other components in our application, those other components are the props.children and they get output right here, nested inside the provider component.
So whatever components are inside here now will now be provided with values that they can use. And the values that they can use are determined by the value attribute right here. So in our case, that would be our store and our set store function. So this component could wrap our root App component, and therefore it's wrapping our entire application, and that means that any component in our application will be able to access this context value, this store. So our product page could access it, the cart page could access it, and any other component inside the app could access it. And the way it accesses it is by using a function called useContext. So we'll see that later, but for now, let's set up our context to look a little bit like this.
Implementing the Cart Context Provider
So the first thing I'm going to do is create a new folder inside src, and that folder is going to be called context, and any context files we create are going to go inside here. So, new file, and we'll call this cartContext.jsx. And inside here, we pretty much need to do everything we just did in the Solid playground. So let me go over there and I'm going to copy all of this stuff right here and paste it in.
And we also need to import a couple of things as well, so these two things at the top. So let me grab those and we're going to paste those in as well. So we're creating the cart context right here using this function createContext that we import. And then we create this function, which is a component called CartContextProvider, and we're taking the props. We set up a store, which is an empty array at the minute, we will add an item in that in a second. Then we return a template which is the CartContext.Provider which comes from this. And we pass a value to everything.
Now, at the minute we get an error right here, and that's because stupidly I've tried to return two values here and they should be wrapped in an object so they're properties on an object. So now we have an items property on that object and a setItems property which are these two things right here. So we wrap this around all the children. And we need to export these things actually. So I'll export this at the top, this cartContext, because we might need it in another file. And also this one. We do need this in another file because this is going to wrap our App component inside the index.jsx file. So I think that's pretty much it for now. Let's save this.
Wrapping the App with the Provider
Let's go to index.jsx and down here in between the Router and the App, let's do the CartContextProvider, this thing right here. That's going to import it for us. And let's put the closing tag after the App, like so. Alright, so now this App is being wrapped by this context provider, meaning it's going to have access to these values right here. So how do we do that? How do we access those values in other components inside our project?
Consuming Context with useContext
Well, the way we do this is by using a built-in function called useContext. So let me go to the cart page where we want to use that context, and I'm going to use that function. So we say const and then we destructure what we want back from the context. In our case, I want the items right here. So I will say const { items } = useContext() and invoke that. Now we need to import that from Solid. So up here, we'll say import { useContext } from 'solid-js', like so.
Okay, so inside here we need to say what context we want to use. Well in our case, that's this context right here, the CartContext, and we exported it so we can use it over here. So we'll say CartContext, so we click on this to import it, and now we're getting the items back from that context. Now at the minute, that's going to be an empty array, but we could add some kind of dummy element in here. So I could do an object, and we could do a title, and we'll just say Test Product. And then we'll do a quantity property, which is going to be how many of these we've got in the cart, and that could be two. Then we'll do an id that can be a 100, doesn't really matter. And then we'll do a price of the item, which could be 15, again, doesn't really matter.
So now we have this product inside the items array. What we could do is use that product by grabbing the items and then cycling through the items down here. So I'm actually going to paste this in because we've seen this kind of code before. So we'll use this For component and we'll say for each items, and then for each item, we fire a function and grab that item. We output a paragraph for each one where we output the item title, the item price, and the item quantity at the end. Okay. So I'm going to save this now and see if this works. Let me go to a browser and okay, we can see now we have that Test Product in the cart and there's a quantity of two here as well. Awesome. So that's working.
Refactoring with a Custom Context Hook
However, there's a better pattern of using context in a component. Because at the minute, what we're doing is we're using this import right here, let me just cut it, and we're also using this import right here. So we're importing two things to use the context right now. What we could do instead is go back to this context file and we could make a little helper function which we export. So let me say export function, it's going to be called useCartContext, like so, and all it's going to do is return useContext, and then we're going to pass in the CartContext, like so. So we're doing the same thing right here as we are right here.
What that means is we can get rid of these imports right here, get rid of that. And then over here we need to import the useContext thing from Solid, so let me put that there. And what we can do now is just use this thing right here, useCartContext, inside the component now. So I could paste this in. In fact, what I'll do is delete some of that so that I can click on this to import it. So we're importing that one thing now and we invoke that. And that's going to return the same things to us. So const { items } = useCartContext(). And that's a bit easier. In whatever component now that we want to use this in, we just have to import the one function. So you might see that pattern a little bit.
All right. So now we have the items. We're doing the same thing. We're going to cycle through those. Yep, that works. What I'm going to do is add one more item. So let me copy that and paste it down here. We'll say Test Product 2, quantity three, ID 101, and the price is 25. Save that, see if it works. I'm going to refresh. Yep, we get a Test Product 2 as well. Awesome.
So that's how we can create a context, and now we could use that context value in any component. It's shared between the different components, anywhere in the application we can use it. So in the next lesson, what we're going to do is we're going to access the context in the product page, the product details page, and use the setItems function to actually update this store value so we can add items to the cart.