React 16.8+
Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.
You can reuse custom hooks in functional components, which lets you write less repeated code.
Function components
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
}
const Welcome = props => <h1>Hello, {props.name}</h1>
React.useState()
import React, { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
A way to create stateful function components
default value
Previous state as an argument
setCount((prevCount) => prevCount + 1)
When using the React.useState() hook's setter function, we can either provide
- a value, which will be the new set value
- or a function, where the returned value will be the new set value and the first argument is the previous state
setCount(10)
React.useEffect()
The Effect Hook lets you perform side effects in function components.
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
React.
useEffect()
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("The component has mounted");
return () => {
console.log("The component has unmounted");
};
}, []);
useEffect(() => {
console.log("The component has updated");
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Lifecycle hooks in function components
Dependency array
Detect changes to a certain state variable
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const [cakes, setCakes] = useState(0);
useEffect(() => {
console.log(`The count has changed to ${count}`);
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment count
</button>
<p>You have {cakes} cakes</p>
<button onClick={() => setCakes(cakes + 1)}>
Increment cakes
</button>
</div>
);
}
Only runs when count is changed
Fetch external data on mount
import React, { useState, useEffect } from "react";
import axios from "axios";
function Example() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const fetchPosts = async () => {
const posts = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
setPosts(posts.data);
};
fetchPosts();
}, []);
return (
<div>
<pre>{JSON.stringify(posts, null, 2)}</pre>
</div>
);
}
Be careful about unwrapped dependencies
import React, { useState, useEffect } from "react";
import axios from "axios";
function Example() {
const [posts, setPosts] = useState([]);
const fetchPosts = async () => {
const posts = await axios.get(
"https://jsonplaceholder.typicode.com/posts"
);
setPosts(posts.data);
};
useEffect(() => {
fetchPosts();
}, [fetchPosts]);
return (
<div>
<pre>{JSON.stringify(posts, null, 2)}</pre>
</div>
);
}
triggers an infinite loop
to prevent it, we need to use React.useCallback()
React.useCallback()
function Example() {
const [value, setValue] = useState('');
const handleChange = useCallback((event) => {
const value = customInputHandler(event);
setValue(value);
}, [])
return (
<CustomInput onChange={handleChange} value={value} />
);
}
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders
Dependency array
useCallback() with useEffect()
function Example() {
const match = useRouteMatch();
const { id } = match.params;
const [post, setPost] = useState(null);
const handlePostFetch = useCallback(async () => {
const result = await axios.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
setPost(result.data);
}, [id]); // function gets re-created when id changes
useEffect(() => {
handlePostFetch();
}, [handlePostFetch]); // the action gets called once handlePostFetch is re-created
if (!post) return <p>Loading post..</p>;
return (
<div>
<h1 className="title">{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
React.createContext() and React.useContext()
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
React.createContext() and React.useContext()
import React, { useState, createContext, useContext } from "react";
const CountContext = createContext(0);
function CountContainer() {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={count}>
<CountRenderer />
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</CountContext.Provider>
);
}
function CountRenderer() {
const count = useContext(CountContext);
return <p>You clicked {count} times</p>;
}
Custom hooks
- They allow us to abstract logic from components (similar to HOCs) and re-use it
- Outsourcing logic lets us have smaller components with a clear purpose
- Abstracting early helps prevent duplication
- It's easier to debug errors with a clear source, rather than inspecting a long component
useEscapeKey() custom hook
const useEscListener = (handler: Function) => {
const handleEscape = React.useCallback((e: KeyboardEvent) => {
const { key, keyCode } = e;
if (key === 'Escape' || keyCode === 27) handler();
}, [handler]);
React.useEffect(() => {
document.addEventListener('keydown', handleEscape, false);
return () => {
document.removeEventListener('keydown', handleEscape, false);
}
}, [handleEscape]);
};
useEscListener.tsx
const Example = () => {
useEscListener(() => console.log('The Escape key has been pressed'));
return (...)
};
Custom hook + Context
import React, { useContext } from 'react';
import UserContext from '../context/UserContext';
/**
* A reusable hook to handle fetching user from the stored parent provider
*/
const useGetUser = () => {
const user = useContext(UserContext);
return user;
};
export default useGetUser;
import React from 'react';
import useGetUser from '../hooks/useGetUser';
export const Header = () => {
const user = useGetUser();
return (
<p>{user.id}</p>
)
}
import React from 'react';
import { UserProvider } from '../context/UserContext';
import { UserProfileService } from '../helpers/UserProfileHelper';
const App = () => {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
UserProfileService.getInstance().GetUserProfile().then(user => {
setUser(user);
})
}, []);
return (
<UserProvider value={user}>
{children}
</UserProvider>
)
};
Re-render basics
A component re-render is triggered by its parent's re-render, a change of state, props or data of a context it's subscribed to.
We can alter the re-rendering behavior by memoizing the values we pass down - be it using a context or props.
React.useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
useMemo() in a component
const ExampleContainer = () => {
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return (
<div>
<h2>Some heading</h2>
<p>Some description</p>
<ExampleChildRenderer value={memoizedValue} />
</div>
)
}
Imagine we have an ExampleContainer with some expensive calculating logic. We also have an ExampleChildRenderer component, that's relying on this calculated data.
If we wrap the data in a useMemo (and provide the dependencies), we will minimize the number of times ExampleChildRenderer will try to re-render. Other frameworks could call this value computed.
React.memo()
React.memo is a higher order component.
If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
https://reactjs.org/docs/react-api.html#reactmemo
It's the equivalent of a PureComponent.
React 16.8+
By Dana Janoskova
React 16.8+
- 716