Image for post
Image for post

Hooks

  1. Resuing logic: When multiple components need to share logic, using Higher-order components or Render props is the first approach that comes to mind. However, as the app grows, adding more and more higher-order components is not only difficult, but it also leads to wrapper hell.
  2. 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.
  3. 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.

React hooks were introduced to reduce these problems by providing a stateful primitive simpler than a class component.

Implementation — Using the React hooks

UseState:

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

The state, need not be a primitive variable, it can also be an object, however, when an object is being updated, care must be taken to destructure and update all the keys.

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:

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)} />
</>
);
}

The infamous lifecycle methods can also be replicated with the help of `useEffect` hook by passing an empty dependency array. Note that when multiple useEffect functions are registered, they are called in the order in which they were registered.

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)} />
);
}

Any API calls or `eventHandlers` can be set or unset using this pattern.

UseRef:

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>
</>
);
}

The main advantage of useRef is that these variables are not tied to component rendering, and can be used as normal class variables. This can also be often used to check whether a state update is happening while the component is unmounting. This could happen when an API call is slow to return, but the user already moved away from the component.

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:

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:

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>
}

The component Hello is rerendered only once, after that, even if the App component is rerendered, Hello remains the same.

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>
</>
);
}

The function will not be computed every time the component renders when count updates.

UseReducer:

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:

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:

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

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

In the above example, useForm can be used to create a bunch of form variables like email, password, name etc. These values can all be handled by just one custom hook instead of writing multiple functions and updating them multiple times.

Caveats

  1. Conditional statements or loops cannot be used inside hooks.
  2. Not all packages can be used directly with the components implementing hooks. A custom hook must be developed for the sake of usage.
  3. Testing with hooks can be tricky.

Written by

CODER | BLOGGER | ARTIST | GHOST

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store