Introduction to State and Signals
Okay then gang, so in this lesson I want to talk a little bit about State Management in Solid.js and the way we create States in components. And for those unsure by what I mean when I talk about State, I mean any kind of dynamic data or values we might use in components. Now that could be an array of blogs that we want to output to the browser where each blog will be an object. It could be a Boolean value which toggles true and false when a user clicks on a button to show or hide some kind of UI element like a modal. Or it could be something like the username of the currently logged in user. Basically state is any kind of data we could use in components that may or may not change over time and have some kind of an effect on the UI.
And when that state does change, because of how reactive Frameworks like Solid, React, View work, those values are automatically updated anywhere they're used in our templates.
Now in solid.js we use signals to store reactive data or States. Now if you're coming from React then this is a little bit like the equivalent of React's useState hook. But using signals can be more performant because they allow granular updates wherever that signal data is used, unlike useState in React which causes a complete rerun of entire component trees where the state is used.
How Signals Work Conceptually
Now I don't want this to be a big deep dive into signals and how they work under the hood, but just so you have a basic mental model the general premise is this: whenever we use a signal like this to store a value, that signal can automatically track any location which uses its value. That might be in one place in the template like this or it could be in several places around your application. That doesn't really matter. The signal will just track each location that uses its value.
And notice also that when we do access the value by the way, we actually use a function to get that value. That's because the createSignal function actually returns a getter function right here and not the value directly. So to access the value we invoke that getter function whenever we need it like down here in the template, and that will get the value for us and output it in the template. So we might have several different places that use this bit of State, this signal, by invoking that getter function. And then when that signal value changes, it notifies all of those different places in the application that uses it and those places can then be surgically updated with the new value. So when the signal value changes, there's no need to rerun entire component functions again to re-render everything, and unlike in React with useState which does re-run the entire component tree.
So on the surface you can think of createSignal as a more performant version of useState if you're coming from React. If you're not coming from React, then just think of createSignal as a way that we can locally store and track any data or values that might change in our application over time. And we can then use those values in the template or in functions or wherever else in the application we might need them.
Example: Creating and Updating a String Signal
So let's have a look now at a few examples. Now before we do that, just two things. Notice I've been calling this useSignal but it's actually createSignal. That's just because I've been using React too much and useState, I call everything 'use whatever'. But no, this is createSignal. And the second thing is we're actually on the Solid JS playground right now. So this is just a place we can play around with solid.js. It's just playground.solidjs.com. So it's good for practicing. Okay, so what I'd like to do now is change this value. And the way we change this value is we use this setter function, which is the second argument or the second value rather that we get back inside this array. So let us now use a setTimeout to fire a function, and that function is going to fire after a certain amount of time. So let's say after 1000 milliseconds, which is one second, and we just call that setName function and pass in the new value. That's all we do. And that's going to update the value. So I'm going to go to Output over here and then back to results so this starts again and notice after one second it goes from Luigi to Mario. So it updates this place in the template surgically when this changes.
Example: Working with a Boolean Signal
Awesome. Let's do another example. So this time I'm going to say const, um, we'll just call it bool and setBool because this is going to be a boolean. This is me being super inventive. createSignal, which by the way has to be imported from Solid.js if we want to use it. So this is going to be true to begin with. And again, we're just going to use a setTimeout I think. So let me copy this and paste it down here and get rid of this because we don't want to set the name anymore. Instead what we want to do is set the bool. So, setBool and it's currently true, is it? Yep. So then let's change it to false.
And by the way, if we want to output this—setPool is not defined—all right, okay I misspelled that. setBool. Okay, if we want to output this down in the template we can do. I'm just going to do another paragraph tag right here and inside that we'll say The boolean is... and then if we output the boolean it would be bool right, and we would invoke that. Now we don't get a value here and that's because it's not a string. We can't just output a boolean. So we have to use the toString JavaScript method to do that. And now we can see it's true, but it changes to false after one second. I'm going to change this to two to stagger those changes.
Three Different Ways to Update State
Now I also want to show you different ways we can update the state or the signal. So the first way is just to pass in the new value directly. Another way is we can use the previous value. So I could say setBool and inside here I could say !bool() and invoke it. So this is getting the current value of this signal and we're reversing it because we use this exclamation mark. So if it's true to begin with then it's going to become false after two seconds. So what I'm going to do is go to output and come back over here again. And we can see it's true to begin with, then it becomes false. Awesome.
Now, if it's false to begin with, it should become true. So false, and then true. So that works. Now the third way we can update this is by using a function. So setBool right here, and then we can pass into this a function that returns a new value. So it could return true for example, and that would set it to be true. You can see that right here. Okay.
Now if we get rid of this, what we can do is also take in the previous value. So I could say prev right here, and you can call this what you want, and I can say !prev like so. So it will take the previous value and it will reverse it essentially. So it was false. If we go to output and then result, you can see false, it should go to true after a couple of seconds, which it does. Awesome.
Example: Incrementing a Number Signal
All right, so let me do another example. This time we'll use a number. So we'll say const age and then setAge and we're going to set that equal to createSignal again. And the age is going to be 25 to begin with. Now I want to output that down here. So I'll say the name is {name()} and the age is {age()} and invoke that, remember. So we can see the age is 25 now. And same again, I want to change this age after a certain amount of time, but I want to keep on changing it. So instead of a timeout, let's do an interval. So setInterval, and we're going to fire a function every one second we'll do, and every one second we basically want to increase the age by one. So I could say setAge right here, and then pass in age() + 1 each time. So let me go to output and result again so we get rid of those errors. And now you can see every second it's increasing by one. Awesome.
Using Signals with Objects and Their Limitations
Now so far we've been using some simple types inside createSignal: string, boolean and a number. We can also use objects and arrays, but createSignal is not really optimized for deeply nested objects or arrays. So let me show you that we can do it first of all. So I could come down here and I'm going to paste in this const right here. So we call it person, then we have a setter function called setPerson, equal to createSignal and then we pass in an object right here. And the first property is the name property which is also an object, and the first name is Brandon, the last name is Sanderson, the age is 45. Apologies if that's too old, I don't know his age, just totally guessed. But we have an object now stored here and this is a signal.
Now, if we wanted to output this object or the different values from the object we could do so. I could say name is, oops, is and then in curly braces it would be person() invoke that and then we want .name.first right? Because the name property, then the first name. And then after that we'll output the second name. So we'll say person() again, invoke that, and then .name.last. So you should be able to see over here we see Brandon Sanderson. Okay, so that's good. However, what if we want to update just the last name? How would we do this? Well, we'd have to basically recreate this whole object, wouldn't we?
So I'm going to copy this and I'm going to use a setTimeout again. So setTimeout, and then we will fire a function, and this will be after three seconds. And then inside here we're going to use setPerson. So I just want to copy this object rather. So let me grab that and then I'll say setPerson and then I'll paste in this object. Now then, all we want to do is update the last name, so I could say 'Williams'. And that's what we'd have to do. We couldn't just pass in the last name into this or we couldn't say name.last is equal to Williams or something like that. We'd have to basically rewrite the value here. So that's the kind of drawback, that's why these signals, createSignal, is not really optimized for deeply nested objects or arrays. And there is another function we can use instead called createStore, and we'll look at that later on in the course. But this still works, you can see it's changed to Williams now. It's just that this way of updating it isn't great. So this is not fully optimized for objects or arrays, but it is good for these kind of values. For objects and arrays we'll look at createStore in future lessons.