So, like I said, my name
is Tanner Linsley. I am from Utah and I love JavaScript so much. I also love React even more. I'm happy to be able to talk about
React at a JavaScript conference. I'm on GitHub probably
way too much, writing way too many
open-source libraries. I'm also on Twitter and YouTube, so you can come say
hi to me there if you want. I know that you're
all probably feeling like... you can't see my screen. Yeah. You're probably feeling
a little bit like my son here, just really tired and wanting to
consume that last little bit of JavaScript. Just do your best. I know it's hard but we're going
to make it through it together. Because today I'm talking
about React hooks and more specifically
custom React hooks and I'm so excited about this and I hope you are too. I know it's been a noisy year. You guys probably feel like... whether you love React or not
you probably feel this way. So, I'm going to do my best today to make sure that this is not
just another React hooks tutorial. It's going to be fast because I have a lot of
content to go over and there's going to be a lot of code. Hopefully it's a lot of fun. I'm going to be saying “use” a lot. So, it's hard, it's a tongue twister. You'll see. So, before we get into
the bulk of my talk, let's go through React Hooks
Basics really quickly. Classes are out. We no longer write with classes. Everything is just a function now
and that is so nice. It makes us so that components
that used to look like this to use State don't have to look like that anymore, we can just use useState. It's so much better in my opinion or we can use useReducer. Class Lifecycles are
no longer a thing. I'm so glad to be done
with these things. Also, we don't have to do manual
change detection anymore with the old class lifecycles. We can just use the useEffect
function from React, pass it some dependencies and everything will just
synchronize for us. It also comes built-in with memoization
and computed state and things around memoizing
values and functions. Where we could do
this with React before but now it's built in. So, we can make sure that we're
only computing or doing work when we need to be doing it. And last, the most important thing
that I'd like to talk about today is Custom Hooks, which a custom hook is really just a
function that runs inside of a component that can run other hooks
or other functions like useState, useReducer, whatever. And they can be recursive,
they can go forever. You can have a custom hook
call another custom hook call another custom hook
call another custom hook and everything will just come back in and it just works. It's amazing. It makes patterns like this that before we had to
nest all these callbacks and create higher-order components
and render props and functions as children, all that gets to go away and we're simply left with
a couple of function calls that we can make inside
of our components. Custom hooks are so powerful. I believe that they give us
a couple more affordances that we didn't have when we
were using class components. I think that they encourage us
to build our own solutions because it makes building
our own solutions easier. The API is more expressive. I also believe that building hooks,
custom hooks is way more portable and shareable than building custom components and trying to move logic
around with components. The other great thing is that custom
hooks can be component aware and abstract essentially anything
that we want them to. This makes them very powerful for integrating with essentially
anything we want to integrate in the outside world
away from React. And last but not least, I think that they highly encourage
rapid iteration for some of the reasons that
I'm going to talk about today. So, the biggest reason that I believe
that all those things are true is that hooks, that with the introduction
of custom hooks, I think that we're going to see
or have already seen a return to components
handling user interface and hooks handling business logic. And if this seems like a no-brainer,
you're ahead of the curve and if it doesn't, that's what I'd
like to talk about today. Through so many migrations of old
projects to hooks going 100% hooks and new greenfield projects where I was able to experiment with
new patterns around custom hooks and see how far can we push
the limits of this new API, I've discovered a couple of use cases that have really sparked new interest
for me with this hooks API. Most of them around
building portable UI utilities, a few of them,
well actually a lot of them, around managing global state
and server state and also encapsulating business
logic for our applications inside of these custom hooks. And I want to share with you a
couple of these use cases today to show you how custom hooks can really change the way that you
think about writing your applications and to be more productive. Why not start off with something
that is so hot right now, you can't ship an app without it and it sometimes is the first
thing that we all think about, probably the most important
thing we all think about, that's Dark Mode. You can't ship without it, right? This is so hot right now. Everybody wants a dark mode. Everybody's got that toggle
at the top of their site, you go between light and dark mode. It's pretty easy to do. You can just set up some
state and toggle between it with a function set is
dark to true or false. It's really that easy. I wanted something a little more
sophisticated for my dark mode. I don't know if you guys have
ever seen matchMedia, but it's an API that you can use to match
CSS media queries in JavaScript and detect certain things
about the users environment. Right here we have a matchMedia that's detecting whether the user
prefers a light or a dark theme and we can actually set up
our state to start with that, so that if the user has a dark or light
theme preference on their device, our state now starts
with that preference and they can toggle between it. But who has this enabled
on their computer? This auto mode. I do. And I love it and I thought wouldn't it be
amazing if we could make it so our light and dark themes would actually react to that
automatic mode on our devices. Turns out it's not too hard. We can take an effect
and put our matchMedia inside it and listen to changes
to that matchMedia and update our dark variable
whenever that changes. And if we put all that together
inside of our app right here, it's all just handled for us now. The Sun sets,
we go into dark mode. The Sun rises,
we go into light mode. It's actually pretty fun to play with. But the problem
I see here is that a lot of this logic is now taking up
space in my app component and I don't really like that talking about “clean code”
opinions here, but this I see is a great example of where
we can make our first custom hook. We can actually just take all that logic
and rip it out into its own file, we'll call it useDarkMode and everything in here
is exactly the same except for what we're returning
is dark at the very end and that makes this useDarkMode
hook extremely versatile. In our app now we can import
that file call useDarkMode and it's going to give us true or false, but it's not just true or false, it's true or false
that changes over time and all of the lifecycle
and updating and all of the intelligence
of that dark mode is built into that hook and all we have to do is call it and that is our first
custom hook for today. That's when everybody claps. So, the next thing that I thought
every application needs, everybody has written
this a thousand times, clicking outside of an element and I don't know why the browsers
don't make it easier to do this natively, but I feel like I'm implementing
this every day. Clicking outside of a menu
or a modal or whatever, right, and then you want to
do something to it. I thought, there's not a better excuse
to write a custom component for this and I thought we might as well
just make the assumption that this is going to be a custom
hook right out of the gate. So, I assumed that I could take
a ref created by useRef, put it on to whatever element
I want to keep track of and then have a handle
or something like console logging, hey, you clicked outside of this thing and I could pass those two things
to this useClickOutside hook and it would just work and I wrote this actually before
I wrote the useClickOutside hook to see how reliable it was
to design APIs around hooks before even writing them. It turns out it wasn't too difficult. We start with the function signature, we throw in an effect that's going to set up our
event listeners on the document for when we click on the document. Those call our internal listener here that checks to see if the target we clicked is inside
of the element that we're tracking, and if it isn't, it calls our callback. Turns out it just works and we would probably
use this for a little bit and think Wow! This is great,
I'm a genius, but eventually you might
notice something very strange. There's something fishy
going on here and it has to do with
the useEffect dependencies. Now if you're using
the eslint-plugin-react-hooks, which you should be, 99% of the time it does exactly
what you want it to, it auto fills Effect dependencies
for your hooks. So helpful, I love it. You can just hit Save and if you have eslint setup
it will just do the changes for you. And that's what it did here
when we built our hook. Here on the right, you can see it automatically
filled in the callback in the elRef down there
in our dependencies but the problem is that the callback is changing
every single render from where we're using it on
the left side here onClickOutside. We're creating it every single time which means that those
event listeners are firing off. Every single time that we render we're adding and removing
event listeners. That is not cool. Well, it turns out it's pretty easy to just
wrap that in a useCallback from React and it's going to make it so that that callback function doesn't
change unless the dependencies change. There are no dependencies yet. So, nothing's going to change and it will stop creating those event
listeners over and over and over, but this begs the question for me, why do I have to have that
useCallback around that function? This probably in advance educates, but I feel like there's nothing inside
of my useClickoutside hook that cares about this
callback changing and I decided that
that's part of the API. So, I wanted to get rid of it and one of the easiest
ways to do that is to instead of referencing
the callback directly, we can use this pattern of tracking the latest version
of that callback in a ref itself, and when we do that the callback ref is now added
to the dependency array which will never change but the value that
we hold inside it will and that way we're only setting up
and tearing down that event listener when we actually need to when the element that we're
tracking actually changes. So, now there's no callback, we can pass whatever function we
want into that useClickoutside hook and that is our second
custom hook for today. What's fantastic about this is you
can move this around your app, you can import it to as many
components as you want or you can just copy and paste
this into another app. There's no external dependencies
other than React and I do this all the time. Greenfield project, I'm like oh! I really love that hook, I go back to the last project
that I was working on, I open the file, I copy it
and I move it over, it's that easy. I don't even really rely on external
libraries that do a lot of this stuff because it's just so simple. Something that isn't simple is state,
particularly global state. Global state is highly opinionated. There's a new way to
manage global state. Every day it comes out
there's a new library. It's kind of noisy. And we're going to be talking
a lot about global state here for the last part of this talk. Global state in my opinion is something
that's always trying to 1up you. It's because you think
you have a handle on it and then your requirements change and everything changes in your app and it kind of pulls the
rug out from underneath you. This is usually how
global state starts and you're like
Wow! This is so simple. I have a hook, it's going to return
some global state theoretically and then for some reason it can
quickly turn into something like this, not to name names or anything, but this is something
I would try and avoid. I don't want to have to go
to these links to use global state. So, I'm going to take you
on a little bit of a journey of finding global state today and it's going to be so introspective
and enlightening that maybe you'll want to actually
handle your own global state after today. Let's start by creating a global store. We can use React context to
pass values down our React tree, not have to pass them through props. We can start with a store provider
that's just a wrapper around context that will set up some state with
a store and a setStore function right there that we can pass down
through our context value and then we'll have a useStore custom
hook that just wraps around that use context and what that does is now inside of our app
we can declare some initial state, pass that initial state
into our store provider and now that state is going to be
available to all the components inside of it, including the little Todos
component down there. And inside of our components we can consume the store pretty
easily by just calling useStore which gives us the
current state of the store and a way to update it which right now is just setState. setState is kind of boring though and it's prone to break and I don't really feel good about manipulating the store
directly from a component. So, what if we took it
a step further to useReducer. useReducer is really pretty
simple to use in my opinion. Just create a reducer function here. We can do it in our app. We can pass that reducer function
into our store provider we created and instead of useState
now we can just useReducer, pass the reducer through the initial state and instead of a setState function
to directly modify the store now we have a dispatcher we can use. So, in our components instead of importing the setState,
we get the dispatch and now we can dispatch actions and have a little bit more confidence about the actions that are
taking place inside of our app, especially even for components
like an individual to do where we're not necessarily
reading from the store but just dispatching, we can still import the store and get a hold of dispatcher
to send off some events. This pattern is really simple. It will probably get you pretty far, but at the end of the day it might bring
up questions about unnecessary renders. You know, I'm rerendering every
component in my app, every single time that
the state changes even if I'm only dispatching. Well, it turns out we can use
a concept called multiple contexts to aid this a little bit. A Multi-Context Global Store was first introduced to me
by my friend Kent C. Dodds and he's made it popular
through a couple of blog posts which are just fantastic and it describes creating
two separate contexts for your store and your dispatcher and sending them both down the line and creating another
useDispatch custom hook there. So, now inside of our components
if we want the store, we use the useStore hook, and if we want the dispatcher,
we use the useDispatch hook. This makes it so that in components
that are only using the dispatcher they're not going to get updated
every single time the store updates and that will get rid of a lot of weird
unnecessary renders around that scenario. Let's take it a step further. What about a single store? It is probably not going
to scale for very long if you have a lot of global
state in your application, you don't want every single
component updating when this state that you're consuming
updates that global state. So, we can take it a step
further with multiple stores. We can take all of that logic that
we had to create our global store and just wrap it into a function and we can create as many of
these global stores as we want by passing them in
a reducer in initial state. So, if we wanted to do a store, we just pass it the todosReducer, it gives us back the provider
and a couple of hooks to consume it and we're off to the races. We come into our app, we provide our Todos
to the entire app and I want some global state to manage all of my menus
being open and stuff like that. So, I'll make a layout store as well and I'll wrap that around my app and then inside of our component like our Menu that we wanted to click
outside and have it change around, we can hook it up to
that useLayoutStore by both subscribing to
the store to see if it's open and the dispatcher to make sure that we can toggle
it close when we click outside of it. The next step in our
journey is persistence. Now our global store isn't
really persisting anything. This is extremely important because your users are
going to reload the page and everything's going to disappear. So, let's throw in some local storage. Local storage is pretty easy to use
especially if you're using a reducer. So, right here we can instantiate our
reducer initial value from local storage, and also every time a reducer runs, we can save the resulting state
into local storage using a key and now every single time
that your users reload the page they're going to get the last state
that they were left with. I know this is where you guys want to be and I'm working hard
to get you there. Just to make sure
that everybody is awake. Like that! I got some t-shirts. All right. Moving on. What about Remote Data Persistence? So, local storage is great but eventually users are going to
want some remote data persistence, they're going to want to
store their data on a server, they're going to want things to
sync between their devices. They're so entitled. So, let's talk about Server State. Now I say server state because I believe that it is truly
different from global state but it is the same in spirit. Server state introduces
some interesting challenges around moving from synchronous APIs
to asynchronous APIs. We're no longer interacting
with a store or a dispatcher, we are interacting with fetching assets and sending mutations
or queries to the server and expecting them to do things, and along with that
we're going to notice that a lot of our components
where we'd previously wired up things like dispatch and the store that kind of becomes a little bit
of a question mark now, where are we getting those things? Well, turns out that our components are really great places to compose
business logic and user interface together and this can be so easy
that sometimes it's a detriment. We take things like local state markup, UI events, styles, things that I feel are meant
to be in components and we kind of mix them
together with all this state and these external utilities or maybe expensive computation or side effects that are
happening on servers, and we take all that and we smoosh
it together inside of our components and sometimes it can make
them hard to reason about and I'd like to propose
a new pattern today where we need to remember that
custom hooks are free now. There's no cost of adding
a new custom hook other than the cost of abstraction. You can ask Kent C. Dodds how he feels
about the cost of abstraction, he talks about it a lot. But today, I'd like to introduce a new
layer of abstraction to our journey, they're just custom hooks. There's really nothing
to say other than that and they're going to proxy
all of our business logic between our components
and the external services and the external features that
we want to integrate with them. So, in this case, we're going
to take our Todos component, we're going to write
a useTodos custom hook that's going to do all the heavy lifting
for us between our data. What would that look like? We could just start with a useTodo, we can import useTodos
into our component and just expect that
it returns Todos and that would be enough for now. This useTodos hook could
do whatever we wanted to as long as it returns Todos, that's all we care about. The implementation details
are hidden from the component, it doesn't care and it shouldn't. So, if we wanted to implement
our existing in-memory store, we could just import our store
and return the Todos from it and we would be fine, but remember when you talk
about asynchronous state and state coming from the server, that changes a lot of things, especially if we're not
talking about suspense which is a whole other talk, but let's assume that we're
not using suspense and this is what something
like that would look like. We know that it's going to
be loading at some point, we know there could be an error or we know we could have Todos. And as long as we conform
to this function signature, we can actually do a lot
with this custom hook, this useTodos custom hook
that we just made. We could use an effect
and like a tool like axios to communicate with a ref server that would load our Todos for us and we can manage all of the loading
state and everything ourselves or we could use a very common
hook called a usePromise that will let us pass a function
that returns a promise and handle all of that isLoading
and error state for us. Again, as long as we're
returning the Todos, the loading state and the error, any component that's consuming
Todos isn't really going to care and we could do the same thing, take this pattern and apply the same
pattern to anything doing a mutation. So, if we had a todo component that
was going to toggle something, we could have that
useToggle todo custom hook take care of that for us. So, instead of importing
some REST API or axios directly inside of our component, we import useToggleTodo, we call it and it'll give us a function, and you know what? Instead of just giving us function, let's assume it's going
to be asynchronous, it'll give us a function and the loading
state and error state as well and again that unlocks us the ability to do whatever we want in
that useToggleTodo hook. We can do whatever we want
as long as we're returning a function that the component can call and an isLoading and error state. So, we could use our
in-memory store again and just hook it right up
to our Todo store, isLoading and error is false and null
all the time if we're doing this or we can go into the REST paradigm and just use axios to issue off some type of a post command
to our server to toggle this Todo, but this can get pretty
confusing very quickly if you're managing all these side effects, especially when we start thinking
about things like caching. What happens when we're using these
custom hooks multiple times in our app if we need Todos in multiple places, are they separate copies? Are we reduplicating things? Are we creating race conditions? And if we're caching,
when are we refetching that data? There are so many questions
that happen along there. Well, we don't have to stop there. I used Todos custom hook and it doesn't have to communicate
directly with our server, it could use other hooks as well. There's a library created
called React query that helps with scenarios like this. You can use React query to give
it a function that returns a promise, it'll handle caching and invalidation and make sure that you're not duplicating
Todos across your entire app even if you use it multiple times. I mean you'll notice that
the really cool part is that our return signature
is exactly the same. So, we just moved from this weird funky
promise based way of fetching our data to something like React query and all of our components that use the
useTodos hook didn't have to change at all. It's just an implementation detail. Same thing we could
do for the mutations. We could add in React query
for the useToggleTodo and in fact React query handles
a lot of the invalidation for mutations. So, running this mutation would invalidate all of the other queries
on the page getting Todos and it would cause all
of them to update. It's not very unsimilar
to tools like Apollo. And I don't use GraphQL a whole lot, but when I do, I do enjoy using Apollo. And what would it take to
switch to something like Apollo? Well, we could import Apollo
and make a query, use the useQuery hook from Apollo and again we're just returning
Todos as loading an error, we just moved from
React query to Apollo, we didn't have to change
anything in our entire app. So, same thing with
causing a mutation, as long as we're returning that ToggleTodo
and isLoading error, again our mutations don't have to change. So, we were able to just
in the last five minutes, well, go from in-memory
management of our data to promises to React query and Apollo, we can probably even move
to Relay if we wanted, all because we're using this pattern of creating
your own custom hooks to abstract our own business
logic in our apps away from our components
to make them smarter and to make them more reliable. This pattern to me
is extremely powerful. I see basically all the time
in my applications, all my components are communicating
with custom hooks to handle most of their business logic which are then reaching out to
possibly even more custom hooks or external APIs or whatever. This creates a really nice encapsulation
around what is user interface, what is business logic and what are the services I'm
trying to use in my applications. Thinking from this perspective you can start to imagine so
many great custom hooks that you would want to
use inside of your apps, but maybe not something that would
be shareable outside of your app. It's okay if a custom hook is proprietary
to something you're working on if it makes you more productive. So, these custom hooks and the idea
of custom hooks is so powerful, I take things to the extreme and I create tons of open-source
libraries built on custom hooks. Those four are just a few of them. React table is in fact just a hook, it actually doesn't
render anything for you, it just is a hook that gives you everything
you need to render your own table, it's a really interesting concept, but my hope and invitation
to all of you today is to look at your hooks in your apps and think of ways that you can
make yourself more agile, abstract your business logic
into your custom hooks and make your components dumber
thereby making them smarter. So, be sure to check me out at
GitHub, Twitter, YouTube. Come talk to me.
I'd love to chat. And can't forget nozzle.io, that's my company that
I started back in Utah. We build marketing software that helps agencies know
where they rank in Google. So, thanks. [Applause]
ooh nice! queueing up to watch :)