[React] Hooks

Introduction

Available after react 16.8

React Hooks are used in functional components to manage state.

useState returns an array with exactly two elements. First element is the state, the second element is a function that will allow us to update the state, and re-render the DOM.

Important The returned function by useState doesn’t work exactly like setState, as it doesn’t automatically merge the updated value with other values not mentioned in the function, rather, it replaces the original one with an entirely new one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useState } from 'react';

const app = (props) => {
const [ personsState, setPersons ] = useState({
persons: [
{name: "Tom", age: 9 },
{name: "Alex", age: 19 },
]
});

const [ otherState, setOtherState ] = useState('Some other state'); // Add other state to the component state

const switchNameHandler = () => {
this.setPersons({
persons:[
{name:"New Tom", age:10}
]
})
}

return (
<Person
name = {personsState.persons[0].name}
age = {personsState.persons[0].age}
/>
)
}

Concept

Functional with Hooks: hooks enable functional components to have class-only functionalities.

Available after react 16.8

useState()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Todo.js
import React, { useState } from 'react';

const todo = props => {
const inputState = useState(''); // Return an array of two elements, the first is the previous state, the second is a function that can be used to update the state
const inputChangeFunction = (event) => {
inputState[1](event.target.value) // Update the state
}
return(
<React.Fragment>
<input
type="text"
placeholder="Todo"
onChange = {inputChangeHandler}
value={inputState[0]}
/>
<button type="button">Add</button>
<ul />
</React.Fragment>
)
};

export default todo;

Array Destructure and Multiple state. (Better practice)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const [todoName, setTodoName] = useState('');
const [todoList, setTodoList] = useState([]);

const todoNameHandler = (event) => {
setTodoName(event.target.value);
}

const todoAddHandler = () => {
setTodoList(todoList.concat(todoName));
}

return(
<React.Fragment>
<input
type="text"
placeholder="Todo"
onChange = {todoNameHandler}
value={todoName}
/>
<button type="button" onClick={todoAddHandler}>Add</button>
<ul>
{todoList.map(todo => <li key={todo}>{todo}</li>)}
</ul>
</React.Fragment>
)

Use state as an object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [todoState, setTodoState] = useState({userInput: '', todoList: []});

const inputChangeHandler = event => {
setTodoState({ // Update the old state with the new one
userInput: event.target.value,
todoList: todoState.todoList
});
}

const todoAddHandler = () => {
setTodoState({
userInput: todoState.userInput,
todoList: todoState.todoList.concat(todoState.userInput)})
}

All the hooks can only be called at the top(root) level in the component.

useEffect()

useEffect() accepts two arguments:

  • A function as argument which runs when the component runs at the first time. It make sure the function is executed after the render cycle is finished.
  • An array of values we want to have a look at before running the function. Only the value in the array has changed do we want to run again the function.

Runs after every render cycle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {useEffect} from 'react';

useEffect(() => {
axios.get('https://...').then(result => {
const todos = result.data;
const todos = [];
for (const key in todoData) {
todos.push({id: key, name: todoData[key].name})
}
setTodoList(todos);
});

return () => { // Return a function to clean up the side-effects, runs after each render cycle
console.log("Cleanup");
}
}, [todoName]); // Call axios.get only when todoName is changed

Imitate componentDidMount()

1
useEffect(() => {}, []) // Only run once

Imitate componentDidMount() and componentDidUpdate()

1
useEffect(() => {}, [someState]) // Only run once

Cleanup

1
2
3
4
5
6
useEffect(() => {
document.addEventListener('mousemove', mouseMoveHandler);
return () => {
document.removeEventListener('mousemove', mouseMoveHandler);
}
}, []); // Add eventlistener when component is mount, and remove listener when component is unmount

Custom Hooks

  • name convention: start with use
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useState } from 'react';

// hooks/forms.js
export const useFormInput = () => {
const [value, setValue] = useState('');
const [validity, setValidity] = useState(false);

const inputChangeHandler = event => {
setValue(event.target.value);
if (event.target.value.trim() === '') {
setValidity(false);
} else {
setValidity(true);
}
}
return { value: value, onChange: inputChangeHandler, validity };
}

Use costom hooks enables to share stateful functionality through different components.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useFormInput } from '../hooks/forms';

const todo = props => {
const todoInput = useFormInput();

return (
...
<input
onChange={todoInput.onChange}
value={todoInput.value}
style={{backgroundColor: todoInput.validity === true ? 'transparent' : 'red'}}
/>
)
}

Refs in React Hooks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useRef } from 'react';

const component = props => {
const toggleBtnRef = useRef(null);
// toggleBtnRef.current.click(); if we use this way, an undefined error will show

useEffect() { // runs after the first rendering, can get access to the DOM element
() => {
toggleBtnRef.current.click()
}, []
}

return (
<button ref={toggleBtnRef} >
</button>
)
}