Introduction:
As we have already learned that Hooks are the new features added to react 16.8. Hooks help in using states and other react features without writing a class.
In this article, we will learn the APIs for the built-in Hooks in react.
As we have already learned that Hooks are the new features added to react 16.8. Hooks help in using states and other react features without writing a class.
In this article, we will learn the APIs for the built-in Hooks in react.
useState function is used in the following way:
const [state, setState] = useState(initialState); |
This returns a stateful value and a function to update it. The value passed in the initial state is the same as the returned state in the initial render. setState function updates the states. It takes one argument as the new state value and re-renders
an en-queue of the component.
setState(newState); |
In between subsequent re-renders, the first value returned by useState is always the most current state after applying updates. React ensures that the setState identity is fixed and doesn’t change on re-renders. And hence, it’s safe to skip the useEffect or useCallback dependency list.
Functional updates:
When the new state is computed using the previous state, a function can be passed to the setState. The function will receive a previous value, and a new updated value will be returned. Below is an example of setState that use both forms of setState:
function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> </> ); } |
The buttons(“+” and “-”), are using the functional form, and the current value is based on the previous value. If the update function returns the same value as the current state, the rerender value will be skipped entirely.
useState cannot merge automatically the updated objects, like the setState method in the class. This behavior can be duplicated by combining the function updater form with object spread:
const [state, setState] = useState({}); setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; }); |
useReducer can also be used here. It manages state objects that contain multiple sub-values.
Idle initial state:
The argument state used in the initial render is initialState. But if the subsequent render forces to pay off heavy charge, then a function can be used which will be executed on the initial render only.
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; }); |
useEffect(didUpdate); |
Functions are accepted that contain imperative and effect complete code. Inside the main body of the function component, mutations, subscriptions, timers, logging, and other side effects are not allowed. This is generally termed react's render phase. Doing so will lead to the production of bugs and inconsistencies in the UI.
Instead, use the useEffect function. After the render function gets executed, the function passed to useEffect will come into force.
By default, effects always execute after every complete render. But if some values get changed, then we can choose between the effects.
Cleaning up effects:
Before any component leaves, it is often required to clean up the effect resources. For doing this, the function passed to useEffect need to return a clean-up function. For example:
useEffect(() => { const subscription = props.source.subscribe(); return () => { // Clean up the subscription subscription.unsubscribe(); }; }); |
The clean-up function always runs before any component is removed from the UI. This step prevents memory leaks. If any element renders multiple times, the previous effect gets cleaned before any following impact. In our example, a new subscription is added every time the function is called.
Effects timing:
The useEffect function separates itself from the componentDidMount and componentDidUpdate. useEffect function fires itself after layout and paint, in between a deferred event. This helps useEffect from many common side effects like setting up subscriptions and event handlers.
All effects cannot be deferred. Let’s take an example: DOM mutation is visible to the user, and it fires synchronously before the next paint. For such an operation, react uses another function called useLayoutEffect. It does the same as useEffect. The difference lies just in the firing time.
Firing an effect conditionally:
By default, the effects are used to fire after every complete render. In this way, the effect gets recreated every time whenever one of its dependencies changes. Creating a new subscription on every update is not needed, only when the source pop has changed. This can be implemented by passing a second argument to useEffect(array of values where effect depends on).
useEffect( () => { const subscription = props.source.subscribe(); return () => { subscription.unsubscribe(); }; }, [props.source], ); |
Subscription will only be created when props.source changes.
The array dependencies are not passed as function arguments to the effect function. Conceptually, every value referencing the inside effect function is also present in the dependency array. In the future, hopefully, some advanced compiler will be able to do this job perfectly.
const value = useContext(MyContext); |
It takes one argument as a context object, i.e., the value returned from React.createContext. The value returned is the current context value for that context. The current value is noticed by the value prop of the nearest <MyContext.provider> above the tree’s calling component.
Whenever there is an update in <MyContext.Provider>, this hook will lead to a rerender of the latest value passed to it by the MyContext provider. Even if the predecessor uses React. memo or shouldComponentUpdate, rerender will happen to start at the component using useContext.
Following things to keep in mind when using useContext:
Whenever any context value changes, the useContext component will always rerender. If the re-rendering is not pocket-friendly, optimization can be used. Putting it together, the example looks like this:
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } };
const ThemeContext = React.createContext(themes.light);
function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); }
function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); }
function ThemedButton() {
const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } |
const [state, dispatch] = useReducer(reducer, initialArg, init); |
This is an alternative to useState. It takes one argument: a reducer type(state, action) newState, and it returns the current state paired with a dispatch method.
useReducer is preferred over useState, especially when complex state logic involves multiple sub-values or when the next state depends on the previous one. useReducer also helps in optimization of components triggering deep updates because we can pass dispatch down instead of callbacks. Below is the example demonstrating useState action, by reducer:
const initialState = {count: 0};
function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } }
function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); } |
React function ensures that dispatch function is stable and won’t change on rerenders. Hence, it’s better to skip the dependencies from useEffect or useCallback.
Specifying the initial state:
Two ways are there to initialize the useReducer state. Depending on the use case, you can choose one. The simplest way is to pass the initial state as the second argument.
const [state, dispatch] = useReducer( reducer,
{count: initialCount} ); |
React don't use state = initialize argument. The initial value depends on props and is specified from the hook call instead.
Lazy initialization:
As the name states, the initial state can also be created lazily. For doing this, an init function is passed as the third argument. The initial will be sent to init(initialArg).
This step also helps in extracting the logic for calculating the initial state outside the reducer.
Bailing out of a dispatch:
React will bail out on returning the same value from the reducer hook as the current state without rendering or firing effects. Bailing out that specific component, react again needs to render.
Hooks are the particular functions that allow hook-up or use of the react state and function lifecycle features. Hooks cannot work inside classes.
Redux contains the global state and the actions to be performed. While react hooks handle the local component state.
useState hook is a unique function that takes the initial state as an argument and returns the updated entries.
No, it’s not possible to use a hook inside a hook. Doing so will violate the rule of Hooks, which states that no two hooks can be initialized one within another.
In this article, we have learned about the rules in Hooks. And finally, some basic hooks like useState, useEffect, and at last some of the additional hooks.
But, this is just theoretical knowledge. Keeping the theoretical knowledge at our fingertips helps us get about half the work done. To gain complete understanding, practice is a must. To gain a thorough understanding, visit our page.
~Happy Learning
~ Pradipta Choudhury