Introduction to React Hooks
The ultimate guide in using React Hooks
React Hooks are a new addition in React 16.8. They let you use state and other React features in functional components. So in this article we are just going to touch mostly used hooks in React.
In the longer term, we expect Hooks to be the primary way people write React components — React Team
State Hook — useState
useState
lets you use local state within a function component.
- Declare state variable
We can declare a state by simply calling useState method with initial value.
function App() {
const [count] = useState(100);
return <div> State variable is {count}</div>
}
2. Update state variable
We can update the state variable by invoking updater function returned by useState invocation.
function App() {
const [count, setCount] = useState(0);
function change() {
setCount(prevCount => prevCount + 1);
}
return (
<div>
<h1>{count}</h1>
<button onClick={change}>Change</button>
</div>
);
}
3. useState can be used multiple times within a single functional component.
function App() {
const [age, setAge] = useState(20)
const [siblingsNum, setSiblingsNum] = useState(2)
const handleAge = () => setAge(age + 1)
const handleSiblingsNum = () => setSiblingsNum(siblingsNum + 1)
return (
<div>
<p>Today I am {age} years old</p>
<p>I have {siblingsNum} number of siblings</p>
<div>
<button onClick={handleAge}>
Get older
</button>
<button onClick={handleSiblingsNum}>
Add Siblings
</button>
</div>
</div>
)}
4. useState can accept primitive value or object as the initial value passed on to useState function
function App() {
const [state, setState] = useState({ age: 20, siblingsNum: 2 })
const handleClick = val =>
setState({ ...state, [val]: state[val] + 1
})const { age, siblingsNum } = state
return (
<div>
<p>Today I am {age} years old</p>
<p>I have {siblingsNum} number of siblings</p>
<div>
<button onClick={handleClick.bind(null, 'age')}>Get older</button>
<button onClick={handleClick.bind(null, 'siblingsNum')}>
Add siblings
</button>
</div>
</div>
)}
5. It is also possible to initialize a state by supplying a function as an argument to useState(computeInitialState)
function App(jsonData) {
const [value, setValue] = useState(() => {
const object = JSON.parse(jsonData); // expensive operation
return object.initialValue;
})
}
6. If you need to update the state based on the previous state, supply a callback function setState(prevState => newState).
function App() {
const [count, setCount] = useState(0);
return (
<>
<p>Count value is: {count}</p>
<button onClick={() => setCount(0)}>Reset</button>
<button
onClick={() => setCount(prevCount => prevCount + 1)}>
Plus (+)
</button>
<button
onClick={() => setCount(prevCount => prevCount - 1)}>
Minus (-)
</button>
</>
);
}
Effect hook — useEffect
useEffects accept a function which can perform any side effect. So what are side effects?
There are two kinds of side effects:
- Effects that do not require cleanup: Network requests, manual DOM mutations, and logging
- Effects that do require cleanup: Subscription to external data
Functional components don’t have lifecycle methods. Hence, with useEffect componentDidMount, componentDidUpdate and componentWillUnmount lifecycle methods are unified into a single API.
useEffect syntax
useEffect( () => { } );
- You can clean-up function, by simply returning a function from within the effect function passed to useEffect. The clean-up function is called when the component is unmounted.
useEffect(() => {
const clicked = () => console.log('window clicked')
window.addEventListener('click', clicked) return () => {
// return a clean-up function
window.removeEventListener('click', clicked)
}
});
2. You can have multiple useEffect hooks within a functional component.
useEffect(effect1)
useEffect(effect2)
React invokes effect hook on every single render.
3. You can avoid useEffect calls to be invoked by every single render by passing an optional second array argument to the effect function. Then the effect will only re-run if any of the values in array changes.
function PostApp(index) {
let [post, setPost] = useState(null);
useEffect(() => {
fetchPost(index).then(json => setPost(json));
}, [index]);
return post;
}
The above code ensures that we only make fetch requests on mount and when we get a new index.
4. You can run effect hook only once (when a component is mounted) by passing an empty array as the second argument.
useEffect(() => {
// code that runs only once
}, []);
Callback hook — useCallback
useCallback hook returns a memoized or a cached version of callback, which improves the performance of the component. useCallback hook is useful when passing callback from parent to optimized child component that rely on referential equality to prevent unnecessary re-renders of child component. ( e.g. shouldComponentUpdate
). useCallback re-runs only when one of it’s dependencies changes.
useCallback(() => { fooFunction(), [dependencies])
In the below example Dropdown component will always receive the same callback for handleItemClick until there’s a change in data or onClick elements.
const DropdownContainer = ({ items, onClick, data }) => {
// This function will only be rebuilt when
// our "data" or "onClick" function change
const handleItemClick = useCallback(item => {
return onClick(item, data);
}, [data, onClick]);
return (
<Dropdown
handleItemClick={handleItemClick}
/>
);
};
Every value referenced in the callback should also appear in the array of dependencies. The
exhaustive-deps
rule ineslint-plugin-react-hooks
package warns when dependencies are specified incorrectly and suggests a fix.
Memo hook — useMemo
useMemo is very similar to useCallback, but it returns a memoized value that is the result of the function parameter instead of a memoized callback. useMemo is especially useful for expensive operations like transforming API data or doing expensive calculations that you don’t want to be re-doing unnecessarily.
useMemo(() => { functionThatReturnsValue(), [dependencies])
In the below example, we will prevent re-calculating of filtered list each time ListContainer component is rendered until the “items” property changes.
const ListContainer = ({ items, onClick }) => {
// This code will only run on initial render
// and when "items" change
const memoizedValue = useMemo(() => {
return items
.filter(item => item.value < 10)
.map(item => item + 3);
},[items]); return (...);
};
Refs hook — useRef
In functional components useRef hook allow us to easily use React refs. This is similar to the createRef in class components. The difference is createRef will return a new ref on every render while useRef will return the same ref each time. Refs provide a way to access DOM nodes or React elements created in the render method and for keeping mutable variable (like instance variables in class components).
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
const refContainer = useRef(initialValue);
- Accessing DOM nodes or React Elements
The below example manages the focus on text input when user clicks on the button.
const CustomTextInput = () => {
const textInput = useRef();
focusTextInput = () => textInput.current.focus();
return (
<>
<input type="text" ref={textInput} />
<button onClick={focusTextInput}>Focus the text input</button>
</>
);
}
2. Keeping a mutable variable
To keep data between re-renders, functional components can use “refs”, which can hold any value. Mutating the .current property will not trigger a re-render.
In the below example stringVal object hold a reference to a string value,
const HoldStringVal = () => {
const textArea = useRef(null);
const stringVal = useRef("This is a string saved via the ref")
const handleBtnClick = () => {
textArea.current.value =
stringVal.current + "The is the story of your life. You are an human being, and you're on a website about React Hooks";
textArea.current.focus();
};
return (
<section style={{ textAlign: "center" }}>
<div>
<button onClick={handleBtnClick}>Focus and Populate Text Field</button>
</div>
<textarea ref={textArea} id="story" rows="5" cols="33" />
</section>
);
}
Context Hook — useContext
useContext hook helps to avoid creating multiple duplicate props. This specially comes in handy if you need to pass props from parent component down to multiple level of child components.
const contextValue = useContext(contextObject);
In the below example we’re passing down user prop down to two levels,
function UserDetailsComponent() {
var [userDetails, setUserDetails] = useState({
name: "Savi",
age: 30
});
return (
<div>
<h1>This is the Parent Component</h1><hr/>
<ChildComponent userDetails={userDetails}></ChildComponent>
</div>
)
}
function ChildComponent(props) {
return (
<div>
<h2>This is Child Component</h2><hr/>
<SubChildComponent userDetails={props.userDetails}> </SubChildComponent>
</div>
)
}
function SubChildComponent(props) {
return (
<div>
<h3>This is Sub Child Component</h3>
<h4>User Name: {props.userDetails.name}</h4>
<h4>User Age: {props.userDetails.age}</h4>
</div>
)
}
Now, we can refactor the above code using useContext hook.
// First we create context, where we can pass in default values
var UserDetailContext = React.createContext(null);
export default function UserDetailsComponent() {
var [userDetails] = useState({
name: "Savi",
age: 30
});
return (
{/* we wrap the parent component with the provider property */}
{/* we pass data down the computer tree with value prop */} <UserDetailContext.Provider value={userDetails}>
<h1>This is the Parent Component</h1>
<hr />
<ChildComponent userDetails={userDetails} />
</UserDetailContext.Provider>
);
}
function ChildComponent(props) {
return (
<div>
<h2>This is Child Component</h2>
<hr />
<SubChildComponent />
</div>
);
}
function SubChildComponent(props) {
// we pass in the entire context object to consume it
var contextData = useContext(UserDetailContext);
return (
<div>
<h3>This is Sub Child Component</h3>
<h4>User Name: {contextData.name}</h4>
<h4>User Age: {contextData.age}</h4>
</div>
);
}
Reducer hook — useReducer
useReducer hook is another way of handling state. When an event changes multiple states together (eg. isFetching, fetchedData) or the next state depends on previous state ( eg. todos) useReducer hook can be used.
const [state, dispatch] = useReducer(reducer, initialState);
reducer — is a callback function.
initialState — is the initial value.
dispatch — is an alias for action in our reducer
state — is the current state
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'addition':
return {count: state.count + 1};
case 'subtraction':
return {count: state.count - 1};
case 'multiply':
return {count: state.count * 2};
case 'divide':
return {count: state.count / 2};
case 'reset':
return initialState;
default:
throw new Error();
}
}
function MathComponent() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch('addition')}>Addition</button>
<button onClick={() => dispatch('subtraction')}>Subtraction</button>
<button onClick={() => dispatch('multiply')}>Multiplication</button>
<button onClick={() => dispatch('divide')}>Division</button>
<button onClick={() => dispatch('reset')}>Reset</button>
</>
);
}
By this way, when an action is triggered we can find all the logic related to state update in a single place.
Custom Hooks
Aside from the hooks that React has provided, we can write our own hooks to meet with our project’s requirements.
In the below example, we create a useFetch hook to fetch data from an API, which returns loading and data props.
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
} useEffect(() => {
fetchUrl();
}, []); return [data, loading];
};function App() {
const [data, loading] = useFetch(
"https://jsonplaceholder.typicode.com/photos?albumId=1"
);
return (
<>
<h1>Photos</h1>
{loading ? (
"Loading..."
) : (
<ul>
{data.map(({ id, title, url }) => (
<li key={`photo-${id}`}>
<img alt={title} src={url} />
</li>
))}
</ul>
)}
</>
);
}
One thing to keep in mind is to start hook’s name with word “use”.
We provide an ESLint plugin that enforces rules of Hooks to avoid bugs. It assumes that any function starting with ”use” and a capital letter right after it is a Hook.
Rules of Hooks
There are two core rules of using react hooks,
- Only call hooks at the top level
Don’t call hooks inside loops, conditions, or nested functions. This ensures that hooks are called in the same order each time a component renders, which is necessary for hooks to work.
2. Don’t call hooks in normal javascript functions or in class components.
- Call hooks from React functional components.
- Call hooks from custom hooks
Use
eslint-plugin-react-hooks
that enforce these two rules.
Thanks for reading the post! I hope this article has been helpful for you to understand the basics of React Hooks and its usage. Please feel free to comment, share and ask questions.