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 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>
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
setCount((prevCount) => prevCount + 1)
When using the React.useState() hook's setter function, we can either provide
setCount(10)
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.
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
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
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>
);
}
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()
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
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>
);
}
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
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>;
}
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 (...)
};
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>
)
};
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.
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.
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 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.