React Hooks 101
useState
To add some local state
import React, { useState } from 'react';
import { someExpensiveComputation } from './someExpensiveComputation';
const BadgeCount = (props) => {
const [count, setCount] = useState(0);
// To lazy load initial state
const [title, setTitle] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
</div>
);
};
export default BadgeCount;
useRef
To give you same ref object on every render, which can hold a mutable value in its .current
property. Mutating the .current
property doesn’t cause a re-render.
const InputWithFocusButton = () => {
const inputField = useRef(null);
// `current` points to the mounted text input element
const onClick = () => inputField.current.focus();
return (
<>
<input ref={inputField} type="text" />
<button onClick={onClick}>Focus text input</button>
</>
);
};
useEffect
Similar to componentDidMount
, componentDidUpdate
and componentWillUnmount
combined lifecycles. It fires after the browser has painted (i.e. after layout and paint).
- Without second argument, it runs first on mount and then on every re-render. Similar to
componentDidMount
andcomponentDidUpdate
. - With empty array, it only runs once. Similar to
componentDidMount
. - With second argument with any value inside (
[variable1]
),it run on mount and when variable changes - Any functions run in
useEffect
must be included in dependencies list - Resolve exhaustive-deps
const SomeContent = () => {
const [count, setCount] = useState(0);
const [inView, setInView] = useState(false);
const scrollHandler = () => console.log('scroll');
useEffect(() => {
if (isInView) {
setInView(true);
}
}); // Run on mount and then on every re-render
useEffect(() => {
document.title = `You clicked ${count} times`;
}, []); // Run only once on mount and unmount
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
// Cleaning up an effect
useEffect(() => {
window.addEventListener("scroll", scrollHandler);
return () => {
// Clean up the event listener
window.removeEventListener("scroll", scrollHandler);
};
});
const fetchProduct = useCallback(() => {
// ... Does something with productId ...
}, [productId]); // All useCallback dependencies are specified
useEffect(() => {
fetchProduct();
}, [fetchProduct]); // All useEffect dependencies must be specified
return inView ? <div>Some content</div> : <div>...</div>;
};
useLayoutEffect
Similar to useEffect
(same as componentDidMount
and componentDidUpdate
) but it fires synchronously after all DOM mutations and before the browser has had a chance to "paint" those changes. Use useEffect
when possible to avoid blocking visual updates.
useContext
To read the context and subscribe to its changes in the provider component.
- access context via
{Context}.Consumer
. Only accessible within jsx return code
import React from 'react';
import { StatusBarContext } from './StatusBarContext';
const App = () => {
return (
<StatusBarContext.Provider value={"loading"}>
<Home />
<StatusBar/>
</StatusBarContext.Provider>
);
};
// Consume context
const Home = () => {
return (
<StatusBarContext.Consumer>
{(status) =>
status === 'loading' ? <div>loading</div> : <div>Content</div>
}
</StatusBarContext.Consumer>
);
};
import { StatusBarContext } from "./StatusBarContext";
const StatusBar = () => {
const status = useContext(StatusBarContext);
return (
<p>
{status === "loading" ? "loading" : "content"}
</p>
);
};
- access context via
useContext
. Prefer this way as it can use context outside the render block
import React from 'react';
import { StatusBarContext } from './StatusBarContext';
const App = () => {
return (
<StatusBarContext.Provider value={"loading"}>
<StatusBar/>
</StatusBarContext.Provider>
);
};
// src/StatusBarContext.js
import React from "react";
const StatusBarContext = React.createContext("loading");
export default StatusBarContext
import { StatusBarContext } from "./StatusBarContext";
const StatusBar = () => {
const status = useContext(StatusBarContext);
return (
<p>
{status === "loading" ? "loading" : "content"}
</p>
);
};
useReducer
Use when the next state depends on the previous one or complex state transitions. There are 2 ways to initialize useReducer
state:
- pass the initial state as a second argument
const initialState = { count: 0, Total: 20 };
const counterReducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count++, total: state.total++ };
case "decrement":
return { count: state.count--, total: state.total-- };
case "reset":
return init(action.payload);
default:
throw new Error();
}
};
const Counter = ({ initialCount }) => {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<>
<p>Total: {state.total}</p>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
};
- pass an init function as the third argument. The initial state will be set to
init(initialArg)
.
const initializeState = (width) => ({ width });
const initialState = { width: 15 };
const lineReducer = (state, action) => {
switch (action) {
case 'add':
return { width: state.width + 15 };
case 'reduce':
return { width: state.width * 0.9 };
default:
throw new Error('action not found');
}
};
const Line = () => {
const [state, dispatch] = useReducer(
lineReducer,
initialState.width,
initializeState
);
return (
<>
<div style={{ background: 'teal', height: '30px', width: state.width }} />
<div style={{ marginTop: '3rem' }}>
<button onClick={() => dispatch('add')}>Increase length</button>
<button onClick={() => dispatch('reduce')}>Decrease length</button>
</div>
</>
);
};
useCallback
Returns a memoized callback. It is equivalent to useMemo(() => fn, deps)
const memoizedCallback = useCallback(() => someFunction(a, b), [a, b]);
useMemo
Returns a memoized value. It can be used to memoize an expensive re-render of a child.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const child1 = useMemo(() => <Child1 a={a} />, [a]);