Hooks

React hooks, which got introduced in 16.8 versions, are a new way of using state, and others react lifecycle methods in functional components. React hooks were introduced to solve 3 major problems in the earlier versions of react:

  1. Gaint components: A class component contains a lot of code to be executed in its life cycle methods, and sometimes a similar set of logic circles around componentDidMount, componentDidUpdate and componentWillUnmount.
  2. Confusing classes: Understanding ES6 classes the right way can be tricky, and also has additional boilerplate code, just to convert a functional component into a class component.

Implementation — Using the React hooks

UseState:

A simpler way of declaring a state, and state handler in React functional components. useState lets you define an initial value for your state, and provides a state variable and handler function.

const App = () => {
const [name, setName] = useState('Jackson');
return(
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}
const App = () => {
const [{name1, name2}, setName] = useState({'Jack', 'Jill'});
return(
<>
<input value={name1} onChange={(e) => setName(currentState => ({
name1: e.target.value,
name2: currentState.name2
})} />
<input value={name1} onChange={(e) => setName(currentState => ({
name1: currentState.name1,
name2: e.target.value
})} />
</>
);
}

UseEffect:

useEffect function gets called every time a render takes place. This can also be executed conditionally upon any changes to a specific state variable created by the hooks (via dependency array). Note that, the dependency array passed is only looked for a shallow comparison, and changes to the nested objects will not be considered.

const App = () => {
useEffect(() => {
console.log('name variable changed')
}, [name]);

const [name, setName] = useState('Jackson');
const [name2, setName2] = useState('Jill');
return(
<>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={name2} onChange={(e) => setName2(e.target.value)} />
</>
);
}
const App = () => {
useEffect(() => {
console.log('rendering...');

return () => {
console.log('unmounting');
}
}, []);

const [name, setName] = useState('Jackson');
return(
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}

UseRef:

`useRef` is the compliment of createRef from the class components. Just declare the ref, and use it to get a reference to a DOM entity.

const App = () => {
const [name, setName] = useState('Jackson');
const inputRef = useRef();
return(
<>
<input ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => inputRef.current.focus()}>focus</button>
</>
);
}
const App = () => {
const [name, setName] = useState('Jackson');
const isMounted = useRef(true);

useEffect(() => {
return () => {
isMounted.current = false;
}
}, [])
return(
<input ref={inputRef} value={name} onChange={(e) => {
isMounted.current && setName(e.target. value);
} />
);
}

UseLayoutEffect:

This hook works similarly to the useEffect hook, however, it is triggered whenever a DOM nodes properties change.

const App = () => {
const [name, setName] = useState('Jackson');
const [rect, setRect] = useState({});
const inputRef = useRef();

useLayoutEffect(() => {
setRect(inputRef.current.getBoundingClientRect());
}, [])
return(
<>
<pre>{JSON.stringify(rect, null, 2)}</pre>
<input ref={inputRef} value={name} onChange={(e) => setName(e.target.value)} />
</>
);
}

UseCallBack:

This hook is used to prevent re-rendering a child component when the parent reloads(if the child component has dependant functions). Especially when using React.memo. If an array is being iterated over, instead of adding function logic to be handled during the iteration, the method can be passed down to the child component and it can implement the caller logic. This way, multiple method creation can be prevented.

const App = () => {
const [count, setCount] = useState(0);

const increment = useCallback(() => {
setCount(c => c+1);
}, [setCount])
return(
<>
<Hello increment={increment} />
<div>count: {count}</div>
</>
);
}

const Hello = React.memo({ increment }) => {
useEffect(() => {
console.log('renders');
});

return <button onClick={increment}>increment</button>
}

UseMemo:

Essentially used when the computations must be optimized. Let’s say you have a function that recomputes the same value on every render, this process would be tedious and slow. Such recomputations can be avoided with the help of useMemo.

const computeSomethingTedious = (arr) => {
arr.forEach(nextDigit => {
if(Math.square(nextDigit) > 45) {
finalDigit = nextDigit + Math.random(0, nextDigit);
}
})
}

const App = () => {
const finalDigit = 0;
const [count, setCount] = useState(0);
const longestWord = useMemo(() => computeSomethingTedious(data), [finalDigit, computeSomethingTedious])
return(
<>
<div>count: {count}</div>
<button onClick={() => setCount(c => c+1)}>increment</button>
<div>Computed value: {finalDigit} </div>
</>
);
}

UseReducer:

This is an alternative to useState hook and behaves quite similarly to how redux reducers work. The method returns a dispatch function and a value. The dispatch function can be used for updating any value changes that are required.

const reducer = (state, action) => {
switch(action.type) {
case 'INCREMENT': return state + 1;
default: return state
}
}

const App = () => {
const [state, dispatch] = useReducer(reducer, 0)
return(
<>
<div>count: {count}</div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>increment</button>
</>
);
}

UseContext:

If the data must be shared throughout the application without necessarily sharing data throughout all the children.

import { createContext } from 'react';
export const UserContext = createContext(null);

// App component
import { UserContext } from '../userContext';
const App = () => {
return(
<UserContext.Provider value="Hey, this is context">
<Home />
<About />
</UserContext.Provider>
);
}

// Home component
const Home = () => {
const msg = useContext(UserContext);
return(
<div>Home Message: {msg}</div>
);
}


// About component
const About = () => {
const msg = useContext(UserContext);
return(
<div>About Message: {msg}</div>
);
}

Custom Hooks:

Apart from using the predefined hooks provided by react, we can also create our custom hooks as per the application requirements.

export const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);

return [values, e => {
setValues({
...values,
[e.target.name]: e.target.value
})
}]
}

Caveats

While react hooks are pretty amazing, they do have some limitations:

  1. Not all packages can be used directly with the components implementing hooks. A custom hook must be developed for the sake of usage.
  2. Testing with hooks can be tricky.

CODER | BLOGGER | ARTIST | GHOST