Check out a free preview of the full Reactivity with SolidJS course

The "Stores" Lesson is part of the full, Reactivity with SolidJS course featured in this preview video. Here's what you'd learn in this lesson:

Ryan demonstrates how nested stores provide fine-grained reactivity because nested updates are handled independently without the need for DOM diffing. SolidJS also provides an Immer-inspired produce method which allows mutations to occur with a writable proxy version of the store.


Transcript from the "Stores" Lesson

>> Next we're gonna talk about state management in Solid. Luckily for us in Solid, state management is basically the same that we've been seeing inside our components. You can put a signal inside you can put a signal outside and that's how you do state. However, we still have a few mechanisms that give you better control over it.

Both the way you inject state management and to control more granular updates. We're gonna continue actually using Todo as a bit of an example now that everyone has a good understanding so that we can actually understand some of the nuances of using state in Solid. So. I have a Todo example very similar to what we just built maybe a little less pretty.

But it has the same idea where we generally are adding Todos, updating an ID and adding them to our list. And this works pretty much as we'd expect. So we can adjust the Window here. Get a little bit more width. We can add Learn Solid. And we get it list crosses it off same functionality we've been looking at but what you probably didn't notice before and because we didn't look at it, is I've sneakily added a console.log inside our Todo item here.

And this is important because I wanna show you something. I talked about how we had the For component to optimize things. And if I add a Todo, like Learn Solid. I add a Todo. We see Creating Learn Solid. But when I'm checking it, wait, it's calling creating again.

And you're like, this is not what I expected. I don't wanna recreate those DOM nodes. Why is it doing this? I mean, I can add a different Todo as well let's add Hello World. And that one will also add its own logs as well. But you can see that they're not calling each other.

It's not the whole list. It's just the one that's changing is the one that's getting called. And the reason for that is the way that we're actually updating our Todos. If you've noticed we were spreading them in essentially, which is fine spending list is fine, but every time we actually change completed, we actually cloned the todo item, and we lost our reference to it.

And essentially it's getting a new todo item every time. So while we could sort the list, filter it, do all the operations we need without, well filtering is different because you create and remove. But while we could sort the list without create or remove, editing the todo is actually removing that reference.

So with reactivity sometimes you actually want nested reactivity, where we wanna actually update the fact that it's completed independent of losing our reference to our todo. And one way to do that, which takes a bit of work, is if we went through and instead of our todo being just an object like this, we can create signals inside each todo item, so you can have a signal with signals in it.

And I'm just gonna jump ahead here to show you what that looks like because it's definitely a bit cumbersome to write out, so I'll save everyone the effort. Essentially, now when we create a Todo we actually add a completed and a setCompleted, so we're actually creating a signal every time we add it todo.

And similarly when we toggle it we actually find the todo and then call setCompleted on it, so you can nest the reactivity so when we add our todo here what you're gonna see is it's gonna log it on creation now. But when we update it's gonna update the state, and it's not gonna log in again and the reason for that is now that completed is its own signal it knows to only run the effect on that one checked expression and not update.

But as I was saying, this is tedious to do, and you don't wanna do this, generally it would be a lot of work. This is how we used to do things back in 2010, 11, 12 time period. Luckily, JavaScript has evolved a lot from then, and I wanna show you something, a different primitive, it's called the Store.

And what a Store is, it's a proxy and by using JavaScript proxies we can automate the creation of nested signals without anyone having to worry about it. You can basically just treat it like a normal JavaScript object and under the hood it can get that reactivity for us.

crateStore starts a lot like our crateSignal, we can just replace it to begin with and so to create Store our signal what we're gonna do here is we're gonna use createStore and pass in an array. Store is fine as long as it's an object. It's important. They can't be like a string or a number.

Because proxies can only be objects, and it's important because we need to access properties on it. And the biggest difference with a Store is. It is not Is a function anymore, so we have to update it a little bit differently, essentially. So. This part is actually funnily very similar, we can just spread our store now that's not a function and add our new todo.

However, if we wanna update our store, instead of doing all this mapping and immutable type logic, we can use something called nested setters. And what a nested setter is going to let us do is instead of using this pattern this was very inspired by immutable.js and Falcor from Netflix from a while ago, where you can actually express things as a path.

So we could do something on toggle like. We can put in path variables, we can put in functions. For example, we can go each section of the setter responds to a part of the path, so we can go set the todos. Where the, "completed" is the property that we wanna set.

And then we can either pass the value or in this case what we're gonna do is we're going to pass another function. Because on the final setter function here we can basically get the value of completed previously and set it to not completed. Now. I'm gonna say a comma, okay?

This is a little bit much because I'm showing basically all the possible syntaxes for a setter at once. Essentially, if you use pass specific keys, you can pass a function to filter it and you can pass for the last argument either a value or a setter function, all right?

But the key part about this is essentially by using this path based syntax, we have the ability to update exactly the spot that changes. When you do something immutable, you clone everything and you lose that information and what changes. And the reason we have setters is you could think or you could use mutation and just change it directly.

We wanted to keep that readwrite segregation. We want to keep that unidirectional flow. Very important to us to provide essentially immutable interface. The ability to set something or change something is something that the parent component chooses to pass on. If it's not something that's implicit, we didn't want people to just be able to write stuff all over the place.

The same reason why our signals have a getter and a setter method and not just a single function or to do it. So you're probably most confused about this filter. If it makes it any easier to picture. This could be 0 for it. So this would be set 0, the todo at 0, completed to not completed.

So, essentially, we just follow a path through our object to be able to get to it. And I realized when I wrote this almost immediately that this is probably the thing where you're like, this is pretty fancy. But I just wanted to show this because this is actually a very concise way to write this logic.

And now when we have our todos, we don't call them as a function, we just pass them in. And what we're gonna see here when we Learn Solid, and I'm gonna bring this up a bit. It's gonna console log the one time like we're expecting and when we check it we're gonna get the same behavior there as the nested signals we did before.

We're only updates exactly what happens, again cuz only todo.completed changed. So we only update the one fine grain thing. So, generally speaking in Solid, we do have two primitives. I started with signals to keep things basic, but this the same in Vue too. They have ref and reactive because of the limits of the JavaScript language, essentially, you have the simple thing to handle primitives.

And then if you wanna do nesting objects, you have this ability to use these stores. Essentially, for people going, when do I use a Store? When do I use a signal? How do I make a choice? I think about it a lot on things like model boundaries, so if you're just doing some simple logic to pipe through, like we were doing the showMode and all that toggling stuff, you can use signals in most places.

But when you're getting to a point where you're like, I'm gonna model some more substantial data. Maybe it came from a database originally. Lists of models like your set of users all that. Stores are the ideal thing there cuz they can do pinpoint updates. Essentially, you can update any spot on it and keep that granularity.

This is a very powerful tool, and it enables Solid to do stuff like update a specific item in a list without running the list reconciliation logic. And without the end user having to worry that much, just go Create Store. Now you have your Store, you don't have to go through and make a whole bunch of nested individual signals.

So this is a very powerful API. And it can be used inside a components in outside component. Some people actually like using Stores, just in their components as a replacement. Stores actually began as a replacement for the same thing as react creates. Sorry, React state in class components where everything was plain object, then people didn't worry about it.

They just go state, createState, and then just use the Store and put all the variables in it. So, there was a bit of a style consideration. And to be fair, Solid does support some other mutation approaches. Instead of paths, you can pass something, a produce function which lets you do mutable changes.

This is very inspired by Ember.js, which helps create immutable changes but in Solid's case, this actually does granular pinpoint changes. You don't lose that information. But the reason that we do a special function again is this, we wanna make sure that the setter is separate from the read so that we can have better control over applications.

It's a very opinionated approach, and it's one place where we actually do hold a fairly strong opinion. Because this is part of that learning from frameworks like React around unidirectional flow. This is part of how we conquered the chaos of reactivity.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now