Overview
In React you've always had access to state, a tool that allowed you to hold on to temporary values, non persisted changes, with easy ways to rerender your components to reflect those changes. At first, if needing state in a component, you’d need to take advantage of class components and lifecycle methods. With class components the constructor, render, componentDidMount, etc. control the functionality of a certain lifecycle. Giving you control of what functionality happens over stages of the component. On more complicated components, this gets messy and doesn’t allow for a real separation of concerns. For example, a componentDidUpdate() method would require every functionality needed for any updates to the state. In this article we’ll go over the most common React Hooks.
Introducing hooks! With React Hooks, all components should be functional. This rids the need for all lifecycle methods, including the constructor and render. Hooks allow for statefulness in functional components, as well as more control over your logic flow. All hooks must be imported into the file from React.
useState
The first hook to get acquainted with is useState, instead of adding values to the state object inside of the constructor and using setState, you’ll add useState inside your function statement, but before your return. When calling useState, you’ll use array destructing syntax to declare, the first being the name of your new state, then the update function, conventionally preceding the state name with ‘set’. Then you’ll pass in the initial state value inside the useState invocation like so:
const [user, setUser] = useState({});
Now anytime you need to change the value of the state you’ll use the update function, passing in the new value of state, or a function that resolves to the new value of state. Using the updater function is the same as setState, except the updater function is specific to the updated state.
const [message, setMessage] = useState(‘’);
const startState = [‘hello’];
const [messages, addMessage] = useState(startState);
const [isWaiting, setWaiting] = useState(false);
In the below example I'll also use axios, a great technology to help make HTTP requests.
const onSend = () => {
setWaiting(true);
axios
.post(‘/chatbot’, { messages: allMessages })
.then((response) => {
setWaiting(false)
addMessage((curMessages) => curMessages.concat([response.data.message]));
axios.post(‘/chatbot/db’, { message: response.data.message, userId: user.id });
})
.catch((err) => console.error(‘failed sending new message’, err));
setMessage(‘’);
};
useEffect
The second hook to get acquainted with is useEffect, which can take two parameters: the first, a callback function, most commonly an anonymous arrow function, being required, and the second, an array, to include the effects dependencies. Intuitively, whenever useEffect is triggered, the function passed in will be invoked. The dependencies array should consist of any reactive values from the callback. Reactive values include stateful values or other hooks. Meaning, anytime a dependency value changes, that useEffect will be called. useEffect is super helpful in splitting up your component functionality into much more readable and modularized code than using React class components lifecycle methods.
useEffect(() => {
bottomScroll();
}, [messages]);
In the above example, useEffect, calling bottomScoll, will be invoked when the component renders, as well as anytime the value of messages changes.
useMemo
useMemo mixes functionality of useState, useEffect and a memoize function. If you’ve used a memoized function before, useMemo has a similar purpose, holding onto cached values for optimization.
import React, { useMemo } from ‘react’;
function EventList({ events, time }) {
const eventsNow = useMemo(() => filterEvents(events, time), [events, time]);
}
In this first example, we’re creating a function EventList, which is destructuring events and time. Then we’re creating a variable from useMemo called eventsNow. Since we want to call our function filterEvents passing in arguments, we need to pass it into the body of an arrow function, and because events and time are reactive values, they belong in the dependency array. This allows eventsNow to hold onto the result from filterEvents, then any time one of the dependencies changes, eventsNow will keep the cached value until the filterEvents calculation is completed, then any rerendering will take place.
The second example may seems less useful off the bat, but we’ll see how it comes into play.
const [user, setUser] = useState(null);
const userState = useMemo(() => ({ user, setUser }), [user]);
Here we’re creating a state of user, an updater function of setUser, and an initial state of null.
useMemo requires a function with no arguments, as well as a dependencies array. Dependency rules are the same as useEffect.
We’ll see soon why this was useful, but our userState is now an object containing user, currently null, and setUser, which can change the value of user.
useContext
React’s useContext is similar to useState, except it is accessible to any component, including children, inside its provider. This becomes more useful with deeply nested components or stateful values needed throughout the entire app as you won’t have to pass state down the entire tree of React nodes.
Wanting to having the value of the user throughout the app, we’ll reuse the useMemo example above alongside useContext to create a UserContext and provider.
import React, { createContext, useState, useMemo } from ‘react’;
export const UserContext = createContext({});
export function UserContextProvider({ children }) }
const [user, setUser] = useState(null);
const userState = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={userState}>
{children}
</UserContext.Provider>
);
};
Now inside App.jsx we’ll make the UserContextProvider a wrapper for all components. Making the context available everywhere.
import React from ‘react’;
// imports of all components in return
function App() {
return (
<UserContextProvider>
<Login />
<HomePage />
<Profile />
<Events />
</UserContextProvider>
);
};
export default App;
In order to use and update the context inside other components we’d have to do something like the example below. If you don’t need to update the context you can access is with just the first two lines, not needing to extract setUser from userContext.
const userContext = useContext(UserContext);
const { setUser, user } = userContext;
useEffect(() => {
axios
.get(‘/user’)
.then(({ data }) => setUser(data))
.catch((err) => console.error(‘failed setting user’, err));
}, [setUser]);
Thank you!
Hopefully after this overview of the most common React Hooks you’ll be excited to see all you can do with this super helpful tool. If you don’t find the one that’s quite right for your needs, you can just create your own! This is just one of the amazing tools React has for us.
Top comments (0)