React.JS Hooks
A journey through React.JS Hooks
Introduction
My purpose in this article is to contemplate, in a simple and direct way, a large part of the universe of possibilities about hooks, which we have today within the React.js libraries. Therefore, I will assume that you who are reading here already have some knowledge about these three topics below:
- JavaScript
- React.JS
- NPM(Node Package Manager)
A brief summary about React Hooks
If you followed the trajectory of the React libraries, you may have heard of React Hooks. Hooks are functions, and we can use them to handle states, to fetch data and to interact with browser APIs. Hooks also give us a way of writing functions that can be shared across files and across projects, for reusability. So, anytime when you are using a Hook from React or another library, they have to start with use.
To get started with Create React App, you need to do a couple of things. So first we need to open our terminal. So we’re going to type $ node -v. This will let us know what version it is. As long as you can see something higher than 8.10, then you can use the Create React App. The next thing is check the NPM (Node Package Manager), that needs to be version five or higher. Now we’ll run a command that comes with NPM. The $ npx create-react-app name-of-the-project, in this case I’ll call it react-hooks. This might take a little while to install. After the installation we can change the directory into the react-hooks directory and then run $ npm start. This command will start our project in localhost 3000. So this should pop this open in a new browser window, which allows us to see the project running. Once inside the project folder, we can check our package.json file for the react and its dependencies.
The useState Hook
The most common place that object destructuring is used in React is inside of the component that’s rendering Hello World, but we also can pass in to any one of these components an object called props. So that means that I can replace World with props.name. Then I can go back to our index.js file and pass in a name property (Pedro), and then our component will render with the dynamic data.
One way we can shorten up our syntax here is we can destructure the props object. Because this is literally an object. Let’s console.log it to check it out.
I can use destructuring to pop out just the name, and nothing changes.
Destructuring is also used with arrays, and it’s used quite a bit with React hooks. So let’s consider in our index.js file an array of people. Now, typically, if we wanted to access the first item of this array, we’d use its position. These syntaxes are really useful and effective to understand and apply modern React projects.
To keep everything on the same screen, I’m also going to not import my app from that app file. I’m just going to create it in the index.js. What I want to do with that is to add state to our App component. And the way that we do this with React hooks is using a function. And the next thing I want to do is create a constant called result, and I’m going to call the function useState. I also need to import it from the React library. Now, through the console.log let me open up our console.
And looks like our useState call is returning an array. And it has two items in it. One, zero position is undefined and the second is a function. So, I can use array destructuring here to make use of these values. The first thing I want to add to this array is the name of the state variable that I want to keep track of. In this case, it’s just going to be called status. Then, I’m going to pass in initial status to the useState function, “Not Delivered”. Back to the h1 tag, we’re going to say the package is status.
Now, the other thing that the useState call returned was a function. The function is going to be used for changing the state. So let’s call this setStatus. Now, setStatus is going to be the function we can use to set the status of the application. We’ll create a button and it’s going to be called Deliver. Then, we’re going to create an onClick handler. Inside of this onClick method we’re going to call the setStatus and we’ll pass in Delivered.
This is the kind of simplest example of useState works. Every time when it’s called, it returns a pair inside of an array. The first item in the array is the state of the variable, the second is the function that we use to change the state.
Another way we can take advantage of useState, is to use it in combination with inputs. Here we’re going to make the status of a checkbox (checked or unchecked), a state variable. So this time is going to be checked and setChecked, we'll call useState and then the initial state is going to be false. Therefore, we can add an onChange event to our input. It's usually a good idea to look at whatever the value of this variable is, to check the checked variable, if you will, and then return the opposite.
More often than not, our React applications are not a single component. So working with state in a component tree is really an important concept to know about. What we're going to build now, is a star rating component, so that we can rate anything: books, movies, restaurants and so on. We'll create it using a smaller component called a Star. The Star component is going to be what makes up all these StarRating components. And the star that we want to use is actually from a separate library called React Icons, and then install it using npm ($ npm install react-icons). In order to create an array of stars that can be displayed on the page, we need to create a little helper function here called createArray. Now createArray is going to take in the length, so that's just how many stars we want to create. And it's going to return an array of that number of items.
So far so good, now I'm going to pass totalStars into the StarRating component. And I'm going to say that is a 10 star component. Now, instead of just hard coding this, I'm going to say that totalStars should be the value that's passed to createArray.
Let's also add a default property here. So I'll say totalStars = 5, so that if totalStars aren't passed, we're going to use 5 as the default. The final thing I want to add here is I want to add a property for color. Eventually we're going to be able to click on these stars and change their color. We're going to say selected = false, and then inside our Star component, we'll add a property called color, and then we'll say if selected, we want to make this red, otherwise let's make it gray.
We've created a few different stars. Now, what we need to do is handle clicking on them. In order to do this, we're going to create a state variable using useState. First we're going to create a constant inside of our StarRating function called selectedStars and setSelectedStars. Or as an initial state we want to provide zero because when our component renders we don't want to have any of these be selected. Now we need to supply these properties to each one of the stars, because the star is actually the component that's being clicked. We want to make sure it has access to this state variable. So I can pass to the Star a property called selected. Selected here is going to set selectedStars is greater than I. This is going to keep track based on the index of how many stars are selected. Then we're going to add this function called onSelect. Similar to our functions from before that changed state, we're going to need to use that setSelectedStars function. Inside of this function let's go ahead and call setSelectedStars. And then how we want to select the star is by using its index plus one, because we're using a zero-based index in JavaScript, we need to make sure that we're capturing that correctly. The final thing I need to do is pass this function to each star, because like I said that's where the clicking is actually going to happen.
We can make this even more clear if we want to, by adding a fragment here around our createArray function. First let's wrap it in a JSX(syntax extension for JavaScript. We recommend using JSX with React to describe what the UI should look like) expression, then wrap this in a fragment. And now we can add, underneath the createArray function, a paragraph that says selectedStars of totalStars.
The useEffect Hook
Another hook that is exported by the React library is useEffect. useEffect allows you to perform side effects in functions components. A side effect could be something like a console log, or it could interact with some kind of DOM(Document Object Model) API(Application Programming Interface) like history or window. First we're going to replace our star rating with a section that will have a paragraph that says congratulations. Then, we can use useEffect, which will need to set up a state variable. Now we'll set up a name and a function to change the state called setName. Then, we'll call useState with some sort of an initial value. The initial value used here is Jan. When we create that variable, we can then display the name inside of this string. Let's create a button to say Change Winner, so whoever is the winner will be reflected in the UI(User Interface). Then, we're going to add an onClick handler here and pass a function that's going to set the name, my name in the case(Pedro). Now when I change the winner, it'll say congratulations Pedro!
This is all stuff we're familiar with because we've already used useState, but here's where useEffect comes in. We're going to use this function called useEffect, and useEffect takes in a function. So it's going to look like this to get us started. Then we also need to be sure to import useEffect from React. We want to use useEffect to change the document title. The document title's default name is React App and what I can do is say document title equals celebrate name. When I refresh, it says celebrate Jan, and if I click change winner, it says congratulations Pedro! And also it's called this useEffect because it's changed the document title to reflect that.
By default this runs after each render and every update, and this may nt always be what you want. Let's go ahead and pass another argument to this useEffect function, and the argument is just going to be an empty array. Notice how the name changes in the DOM, but the effect is not called. So when you pass an empty array, it means that the effect will only fire once, on first render. You also can pass the name of the variable here. I could say the name, and then when I change the winner, it will follow up by changing the document title.
This is particularly useful if you're dealing with more than one state variable. I'm going to change the document title to a console log message, and then I'll say celebrate name. We'll add another useEffect here, which will take in another function. We need to create another state variable(called admin and setAdmin this time) with the initial state false, and this time we'll say console.log(`The use is: ${admin ? "admin" : "not admin"}`). Now we have two different state variables and let's also add another paragraph and another button. If it's an administrator, then return "logged in", otherwise return "not logged in". The button will say "Log in", and then onClick should call that setAdmin function to change the state to true.
We have a lot of stuff going on here, and what we're noticing right away is that these have fired on the first render, but let's go ahead and remove this dependency array from our first useEffect. So I'll give that a save, celebrate Jan, user is not admin, and click the first one. We'll see the new state, celebrate Pedro, but still get this message, too. My desired effect here, no pun intended, is that if I change the winner, I only want to call this useEffect. That's where the depency array comes into play, we'll call the first useEffect function any time when the name changes and the second one any time the admin changes.
Another use case for useEffect is for data fetching. So let's say we had an API like api.github.com/users. This is just a straight up rest API which returns some JSON(JavaScript Object Notation) data and we want to make a request for this data and then load it into our page. The way I can do that is by using a combination of useState and useEffect. To start we are going to create a state variable for data and setData, then on the first render we want to useState the initial state just to be an empty array. Then we'll return a paragraph that says "No Users". The useEffect always takes in a function, where we're going to use fetch which takes in the URL(Uniform Resource Locators) of the API. Once we've fetched, we need to call .then and we'll take that response and we'll pass it as JSON and again .then to call setData. So if there's some data we want to return an unordered list and use the method map over all these users and display their usernames.
Now what happens if we want to make this a little bit more interesting? Let's wrap our ul with a div so that we can add a button, and this button which says "Removed Data", into the button let's add an onClick event which is going to call setData. Remember all these functions take in a new state, so our new state is going to be an empty array, therefore if I click on the button the data is gone.
But what happens if I remove that dependency array? Even after I click on the button, it's constantly flickering trying to load it again. There will be a bunch of extra HTTP requests caused by us not being supplied with that empty array.
Additional Hooks
In addition to useState, there's another way to manage state variables, and that's another one of these hooks called useReducer. So first, let's import useReducer, and instead of returning all that data, we're going to return an h1 tag, which should say a number. The number that we want to display is going to relate to useReducer here, therefore let's say number and setNumber. But useReducer is going to look slightly different, instead of taking in just an initial state, it's going to take in two arguments. The first argument is actually a function, and the second argument is the initial state. In our example the initial state will be zero and the function that we're going to send as the first argument to useReducer is going to be what we want to have happen when setNumber is called. We want to take the number and the ew number in as an argument, and then we want to return the number plus the new number. Then we'll add an onClick and say setNumber with 1 as argument. Every time I click on this h1, it's going to increment the number.
Earlier in the article, we created a checkbox, and we handle the checkbox state by using useState. We can refactor this a little bit using useReducer, which may help with bugs that could pop up just by working on a team with other people. If we build all the functionality into useReducer, we can minimize the amount that can go wrong. Instead of importing useState, let's import useReducer and we'll replace it in the code. The next step is to call this function toggle, and inside of the useReducer function, again, it takes in two things. Then, I'm going to say that false is the initial state. Instead of using setChecked in this onChange, we're going to replace this function with toggle and now, it's going to work the exact same way.
What's really nice about this is instead of having to embed this logic into all of the onChange events, we can sort of abstract this way into its own function. We know that every time we call toggle, we should do this. As your state gets more complex, it can be nice to consolidate your functionality into a useReducer call. This makes it a little easier to understand and a little easier to work with.
Another pattern you can use with useReducer is to define a list of actions and then dispatch them in the component. So this will look a little different, first we'll start by adjusting our component. Inside of the component, we want to return a message and we want this message to display whatever the state of this message value is. Inside of the useReducer function, we're going to scaffold this to say a couple different things, it's going to take in the state. Then, it's going to take in this method called dispatch, then useReducer will take in a reducer and the initial state. We will define these outside of these parentheses. Now, the easier part of this is to define the initialState, let's go ahead and do that. The initialState will be a message that says "hi". Once we've created an initialState, that'll be passed into the function reducer. A reducer takes in state and an action and then, it returns a new state. So the way that I can define which state it should return is with this switch statement. The switch statement is going to take a look at whatever the type of the action is. This means that we can define the list of the possible actions that we could dispatch. Just think of an action as being like some sort of an event. We're going to send a "yell" action and it should return a message that says "HEY!". Then we can specify a case for whispering. This time we'll return another object with the message "excuse me". Now that we have our reducer defined, nonetheless what I really want to do is to try to dispatch some actions. We will add some UI elements that can be used to trigger these changes.
The reducer function takes in the state and an action and returns a new state, thus the real benefit of having access to that previous state is so that we can use it. I'm going to change this message to a template string and say `HEY! I JUST SAID ${state.message}`. This is going to display whatever the previous state of message is. Now if I click on "YELL" again, it'll say "HEY! I JUST SAID hi". Therefore, we can do the same to the "whisper" button.
As we move our way through all of these different hooks that the React library provides us with, one that we can't overlook is useRef. So useRef is going to allow us to reach out to a component and determine its value. This can be extremely useful, particularly with forms. The first thing I want to do with our form is to import useRef. Inside of the component we'll return a form tag, inside the form I'll add an input with a type of text and we'll add another of these with s type color. Let's also create a button with a text that says "ADD", which will allow us to add a color. What we really want to track in this case is whatever the user inputs into the field text. The thing that they'll input there we want to catalog a sound and whatever we think the closest color to that sound might be. If we want to be able to reach out to this form element and grab whatever the user types in, so "screech" or something like that. I need to be able to access this and capture it somewhere. Thus we're going to call useRef to find that. We'll say const sound equals useRef and then const color equals useRef also. Now the way that we attach these to the form elements is we just add another attribute called ref and we attach that to whatever the name of the ref is. Next, I need to handle submitting this form and to do so I'm going to create a function called submit, and this takes in the event, the submission of the form. I need this because I want to prevent the default behavior of the page, in other words I want to prevent the page from reloading as soon as I click that form. Now in order to grab this value, like screech, is to create another variable called sound value. This is going to be set equal to sound.current.value. The ".current.value" is what's provided to us by this useRef function. So once we create one of these for the sound, we'll do one for color and then we'll say color.current.value. Now we want to not really do much with this but I just want to make sure that I can capture this value, so let's just alert it. Okay, now I'm going to use this in the form, so I'll say onSubmit equals submit.
What's really cool about this is we're going to be able to check in with the values of these form elements. And at this point, all I'm doing is alerting them, but you can think about how you might pass these off to your database or some other sort of external process that's running, you can capture user input.
Another way to handle inputs in a form is to use a controlled component. A controlled component means that you handle forms inputs using state variables, let's refactor our form component to make this work. Now, instead of useRef I want to useState. And again, just to make sure that is working, we want to check the sound and the color, and then upon submitting the form, we can reset the state saying that setSound is an empty string and setColor to "#000000".
Creating Custom Hooks
In addition to pre-build hooks that are supplied by React library, you can also create your own custom hooks. If there's ever a place where you're writing a lot of the same code in your components, it might be possible to extract the logic into a hook. For example, in our previou input elements, we're just getting and resetting values. So we're going to do all of this instead of in this component, in a custom hook called useInput. The first thing we need to do is create a file called useInput.js, and I'll put it into the source folder. Next, I'll import useState from React, now we're allowed to use hooks inside of other hooks or in components, and this is a great place to use one. We need to think about what we want our input to do. Therefore, the first thing is a function that's going to take in an initial value, then we'll use value and setValue as the state variable and the function to change the state. And then the initial value is what we'll send to the useState function. Our useInput has to return something, like when we call useState, we know that to return a pair, it returns an array of the value and the function to change the value. What we want to return, in this case, is first, an object that has the value and an onChange. The onChange here is going to set the value, it's going to call setValue function, which will change the state. The other thing I need to add here is a function that's going to do the cleanup.
Now that we've created this hook, we can use it anywhere. We'll use it first, of course, in our app. So let's open up the index file again and import the useInput file hook. Now, instead of using useState here, we're going to use useInput. Creating a title called tittleProps, then we're going to create a value called resetTitle. And we're going to say useInput, passing an empty string as initial value. As useInput returns an array whose first item is an object with the value and onChange, so these are the properties for that input. Then, the second value is how we're going to reset the title or reset the value of the field. In the input tag, instead of the value attribute, we're going to replace this with a JSX expression, and we'll push in all of the props from the title. Because our function to change the state comes from titleProps, we can also remote onChange. And we want to call resetTitle and resetColor to finish.
There are times when we want values to be available to the entire component tree. instead of passing data up and down the tree, we can put the data in context, so that all of the child components will know the values that are being passed down. So if you're worked with context in React before, this is kind of the new way to do it using React hooks. Let's go ahead and create a component that's going to consume some data from context. First we need to import createContext from react. Now, createContext is what we're going to use here to export const TreeContext (we're going to create this). It's just a little container where we're going to be able to store any data that can be consumed by any of the components that are part of the component tree. The data that we want to create here will be an array of objects and this is going to be super simple, we'll have an ID for each one and a type. Now what we can do is to make all of this data accessible to our entire app. I'm going to replace the React.StrictMode(Strict Mode is a tool for flagging potential problems in an application. Like Fragment, StrictMode does not render any visible elements in the interface. It does, however, enable additional checks and warnings for its descendants) and I'm going to wrap my entire App component in the TreesContext.Provider. We use the Provider to provide the data to the App component and anything nested below it. Now we need to supply a property called value and we're going to pass in the trees so that our component has access to that data. Once we supply that data via the value property, we can use the data inside of the component. To access this data let's go ahead and adjust our app a little bit, let's move our app component out of the index, and we're going to move it to its own file, then we import it into the index file. And hopefully this will mimic what it's really like to work on a project with context a little bit more. If we go back to our app component, we need to take that data from context and make it accessible in this component. The way we're going to do this is importing the tree's context from the index file. Now, what we can do is pull in the use context hook from React. Next, we're going to call const, saying result equals useContext and then pass in the name of the context that we're interested in, let's then console log the result. This must return an object that has trees as one of the keys. Now what I can do is destructure trees from that result, then inside of the component I can add an unordered list, mapping over trees and then for each one of these trees, I want to return a list item, each list item will have a type.
We have displayed some data and we've pulled it in, using a complex hook. So keep in mind that in any component, if we have a thousand component in our component trees (that's a lot of componentes, but you know what I mean), any component that is part of the app, because it's been wrapped in the Provider, we're going to be able to read the value of trees simply by calling use context.
It's also possible to create a custom hook to make this even more reusable. What I want to do here, first of all, is import useContext into the index.js file. The second thing is we still need to create the context, but we don't need to export it, because we won't be using it in a different file. What we want to export is a constant called useTrees. This is our custom hook, that's going to return useContext with the TreesContext. This is going to make all of this data accessible to the component, but what we use it in the component, we're not going to call useContext with TreesContext because that's a lot of typing. All we really want to do is say import useTrees from "./". Now the output is the same, all the data is the same and I can get rid of useContext import, useTrees is simply going to allow us to reuse this functionality inside of components all over our app.
So far we're fetched data with useEffect, and we're also loaded state data with useContext. Now we're going to take a look at how to fetch data with a custom hook. Let's kind of think about the process of fetching data, if we go back in our index file and what we actually want to do is handle one of three states. So if the data isn't available yet, but is loading. Another one to get the data, and a state for if there's an error. Since all of these states exist with the request, we can wrap this inside of a hook. Let's create a new file called useFetch. So now useFetch is going to handle all of these different states. We first need to import useState and useEffect from React. And then this function is going to be exported, useFetch will take in a URI(Uniform Resource Identifier) of some sort of API that we want to fetch from. Now we'll create data and setData, and the initial state will be null. We also want to handle loading and setLoading, and then we want to halde error and setError. Once we have created these state variables, we need to call useEffect, remember, this is going to help us fetch the data. So now we can say, if there's not a URI, we return, so jump out of the function. If we do have one, let's fetch from that URI, we'll call ".then" and take the data, parsing it as JSON, and then we'll call setData. We also want to call setLoading and then send it "true". When our component first renders, the data is loading, then when we fetch the data, it's going to be "false". And finally, we'll add a catch here, if something goes wrong we want to set the error. We only want our useEffect to be called when there's a new URI or on the first page load. To finish we'll return loading, data and error.
Now that I have the useFetch hook created, it's time to use it. Let's create an app component again in our index file. Let's also get rid of the Provider, and instead just render the App. Inside oh this app component, we want to display some data from that API. We'll use the same one from before, and we are also going to pass in the login, as a property. There are a couple things we need to do here, remember our useFetch hook takes in the URI for our API(api.github.com/users). Then, we can chain on this little template(https://api.github.com/users/${login}) here, and say login. So that's whatever the username for the GitHub user is. Now useFetch returns an object with loading, data and error. We also can get rid of createContext and useContext. We need to import useFetch from "./useFetch". We'll say if loading is true, then we want to return h1 that says "loading". Now we need to say, if there's an error, we want to return a pre tag, and it'll say JSON.stringify, we'll pass the error, null and 2 for a little formating. Next, we'll return a div tag, and inside of it we'll add another pre tag. So now, this is returning a big object to me, it's saying that the login through is undefined. What we need to do is pass the login as a property. Now we want to display an image, so that'll use the data.avatar_url, let's also add an attribute to this image tag which says data.login, and that gives us the image thumbnail. What we can do next is display some personal details.
Conclusion
Thank you for joining me on this journey through React Hooks and learning about how they can be applied in various situations. Libraries like React Router, Relay, Apollo and many more, use Hooks as part of their API. So this article will hopefully set you up for success as you explore other areas of React's ecosystem. If you're looking for more on how to develop your own custom Hooks, I'd definitely check out this website called usehooks.com(http://usehooks.com). There you can learn more about how others develop Hooks and how you can pick up some tips and tricks to apply to your own projects. I wish you all the best as you continue your journey with React well into the future!