A common way to share code across Components has been Render Prop Components or Higher Order Components (HOC), but extracting code with Hooks is much more straightforward. You can take code from an existing Component and pull out the hook logic into its own function for reuse.
Instructor: [00:01] Here, we'll start a dev server and run our simple React app of a battery status meter. It uses the deprecated battery status API that still happens to be in Chrome for now. The app will respond to changes in battery level, and tracks if the computer is being charged or not. Let's take a look at the function component for this.
[00:20] It's using a combination of useState and useEffect hooks that are currently defined as part of our component. What if we wanted to use this logic inside of another component? Well, we can introduce our own custom hook for this. What does that look like?
[00:38] It's pretty straightforward. You just make a function. However, there is a catch. Your custom hook has to be prepended with the word use. We'll call ours useBattery. From this point on, you could do whatever you want in your custom hook. For the most part, we already have the code that we need.
[00:58] We'll grab our useState, useEffect, and handler, and move all of that into our custom hook, and then we get to decide what we want to expose to the consumer of this hook. In our case, it makes sense to only send the battery state. We won't expose the updater since the battery status API does that for us.
[01:22] Now, in our main playground component, we'll create a battery variable and set it to the return of the custom useBattery hook. Now, we could run our app. It works, just as it did before. Now, we have a reusable custom hook that we could use elsewhere.
[01:41] Now, let's switch gears and look at a to-do list component that has several side effect features of its own. It supports local storage to persist to-dos. It interacts with the document title to show incomplete items, and responds to keydown events to reveal an "About" dialog.
[01:59] Let's pull those three features into their own custom hooks so that they could be reused at a later point in time. Our to-do list function component has these features already in it. We just need to refactor those parts out into their own custom hooks.
[02:15] Let's start with the useLocalStorage custom hook. We'll accept a key, a default value, and an optional callback. We'll grab the initial to-dos, the useState, and useEffect code and paste that into our function.
[02:32] Since we want this to be generic, we could rename things to be less to-do-ish, like initial value, and use the key provided, and stringify the default value. As for the to-do ID max logic, that doesn't make sense to go here. Let's provide a way for the consuming code to run code from the init, and pass the storage for them to use.
[02:55] Then we'll generalize the state to storage and set storage, change initial to-dos to initial value, update our local storage key, stringify storage, and only update on changes to storage. Then for our API, we'll return storage and the setStorage updater in an array.
[03:18] Now, let's use our custom hook. We could delete much of the code, and instead of using the useState hook, we'll use our custom useLocalStorage hook, passing the to-dos key, an initial value of empty array, and a callback where we reduce all the to-do items to find the maximum ID value.
[03:39] Next, let's create a useDocumentTitle hook, which will be much simpler than the previous one. This will accept a title, and we'll use the useEffect and only run it when the title has changed. Inside our hook, we'll update the document title with whatever title that was passed. That's it. In this case, we don't need to return anything. There isn't much to do.
[04:02] To use this hook, we'll pull out the logic we had before, save off the dynamic title that was being generated, and use that to pass to our custom useDocumentTitle hook.
[04:14] The last custom hook we'll create is a useKeydown hook. The API we'll support is an object that defines which keys we want to listen to and the value that they represent. In this case, a question mark is true, an escape is false, and everything else will be ignored. The second argument will be the initial value of the state.
[04:33] Let's grab the useEffect already defined and come up and create a custom hook called useKeydown with parameters of map and default value, like we talked about. We'll paste the code from our buffer.
[04:45] Here, we'll need to introduce some new state with useState, and destructure match and a setMatch updater with a default value of whatever was passed in. In our return, we'll return our matchState and setMatch updater.
[05:02] Then we'll need to replace the setShowAbout with setMatch, and replace show with pre-match. As for the logic, since we're making things more generic, it'll look quite different. Here, we'll grab the keys of the map and see if any of them match the key event. If so, then we'll use the value for that item. Otherwise, it would just return the previous state.
[05:25] At this point, we should be able to rerun our app and give it a go. The local storage seems to work, the title's updating, and the "About" dialog shows up. Good deal.
With the 'useDocumentTitle' custom hook, the code to compute the incomplete Todos is run on every render, whereas without the custom hook, it was run only when there was a change in the 'todos' - isn't it thereby better to NOT use a custom hook in this case for 'perf' reasons ?