OverReact with Hooks — A hands on session

Devansh Khanwalkar
Walmart Global Tech Blog
9 min readAug 14, 2020
ItPhoto by chuttersnap on Unsplash

It all started with a tweet by Eric Elliott and everyone loses their mind.

React has a setState() problem: Asking newbies to use setState() is a recipe for headaches. Advanced users have learned to avoid it. ;)

React introduced the concept of Hooks in 16.8 which allows us to hook into the lifecycle methods of a functional component. Not everyone is a fan of classes and FYI even a class boils down into a function in a javascript runtime environment. So using a class component just to take advantage of life cycle methods doesn’t makes sense to me.

Scope for this article will be familiarising with most used hooks by a hands on session to understand how we can replace a class component and its lifecycle methods with react hooks.

The Definition

Hooks are functions that let us hook into the React state and lifecycle features from function components. Meaning that hooks allow us to easily manipulate the state of our functional component without needing to convert them into class components. Obviously hooks don’t work inside classes — they let you use React without classes.

Classes confuse both Humans and Machines

Morpheius telling the truth to NEO
Photo by Miro Medium

We know that components and top-down data flow help us organise a large UI into small, independent, reusable pieces. However, we often can’t break complex components down any further because the logic is stateful and can’t be extracted to a function or another component.

  • Huge components that are hard to refactor and test.
  • Duplicated logic between different components and lifecycle methods.
  • Complex patterns like render props and higher-order components.

Agenda

We will be developing a basic counter application just to keep it simple and bare minimum and we will try use hooks. Below is the already built counter using class component.

From here on , i will be explaining more through code and less through writing.

Typical class component that renders a counter | Edit in JSFiddle

We will be covering the following hooks -

  • useState — We will see different examples of using this hook including best practices, using multiple useStates etc.
  • useReducer — An alternative of useState. We will see how we can use this hook to manipulate state just like redux reducers do.
  • useEffect — The ultimate replacement of React class lifecycle methods. We will see how we can replace all those componentDid*#*# methods.
  • useContext — A rather late addition to React’s context API.

Simple useState

useState is “used” to manage state in functional component.

Simple useState example | Edit in JSFiddle

Two highlights in this snippet:

  • we have a very simple function that uses useState and renders an h1 wrapped within a div. The counter is being displayed within the h1 tag.
  • The output will be the value 0 displayed on the screen.

Lets dig a bit deeper..

What happens when we call useState()?

useState takes in an initial state as a parameter and returns an array with two objects — State value and an Updater function.

State value is used within the component wherever we want and it doesn’t have to be an object unlike classes. The Updater function is used to update this state value. Instead of calling setState() method we call this updater function.

That’s smart! | Photo on Tenor

Fact: Under the hood, react preserves this state value. So, when the component is rendered again because of state changes, the latest value is always available.

Multiple useStates possible?

Of course! We can create multiple state variables by using useState multiple times as shown below.

const [counter, setCounter] = useState(0);
const [error, setError] = useState('');
Multiple useState example | Edit in JSFiddle

The counter variable is initialised to 0 and the error variable is initialised to empty string. It returns a pair of values: the current state and a function that updates it.

This is why we write const [counter, setCounter] = useState(0). This is similar to this.state.count and this.setState in a class, except you get them in a pair.

So what Do Square Brackets Mean? Anybody?

const [count, setState] = useState(0);

This JavaScript syntax is called “array de-structuring”. It means that we’re making two new variables count and setState, where count is set to the first value returned by useState, and setState of the second one is an updater function which can be used to modify the state (count in this case).

Using updater function

To update the counter we can use the below code, anywhere, maybe on click of a button, etc. setCounter(counter+1);

We will add two buttons, to the page. One will increase the counter when clicked and the other will decrease the counter.

We will write an increment() and decrement() helper function which will be respectively invoked from the button click.

Updater function for useState | Edit in JSFiddle

Quick Question: Can we use setCounter (counter ++) instead of setCounter (counter + 1) in the above code? Answer in comments.

Using functional parameter in updater function

setState actions are asynchronous and are batched for performance gains. setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

setState alters the state and causes re-rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.

If you need to ensure ordering of events after a setState call is made, you can pass a callback function.
this.setState({ something: true }, () => console.log(this.state))

Functional parameter in updater function | Edit in JSFiddle

Putting error state for negative numbers

Let’s add a feature to show error message, when the counter goes negative.
Let’s create a variable to hold the error state.

const [error, setError] = useState(“”);```

Let’s modify the code to accommodate error state:

{ error.length > 0 && <div className="animated error"> {error} </div> }

Here we are checking for the presence of error and if true we display the message. Lastly, to make it work let’s modify the increment() and decrement() function like so.

Error example multiple useState | Edit in JSFiddle

useReducer Hook

React now provides a built in useReducer hook, which can be used instead of the redux library. It accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method.

Though useState and useReducer both achieve same things but there is a key difference — useState() returns an updater function and useReducer returns a dispatch function.

So when to use what?

Usually in big(complex) applications the state that the containers or components maintain is not that simple. Containers maintain a huge state and they have to deal with with changing states based on previous states. Currently we do it within componentsWillReceiveProps() and dispatch actions based on props and nextProps.

A typical use case will be when different analytics events needs to be fired based on different steps of a progressive form that has steps. It can really get complex like this.

Photo on Tenor

useReducer is usually preferable in such scenarios when you have complex state logic that involves multiple sub-values.

Let’s define our reducer

function reducer(state, action) {
switch (action.type) {
case "increment":
return {
...state,
counter: state.counter + 1
}
case "decrement":
return {
...state,
counter: state.counter - 1
}
case "reset":
return {
...state,
counter: Number(action.value)
}
default:
return state;
}
}

Assume our “complex” state to be this

const defaultState = {counter: 0, someothervalues: "test"};

Now, within our Counter component, create a reducer and initialise it.

const [state, dispatch] = useReducer(reducer, defaultState);

The increment decrement and reset JSX will change too like this:

useReducer Example | Edit in JSFiddle

UseEffect

“Unlearn what you have learned” — Yoda | Photo in Imgur

The useEffect hook allows us to hook into the lifecycle of the component, like componentDidMount , componentDidUpdate , componentWillUnmount. So this one function plays the role of about three lifecycle events from class component.

The key ingredient of useEffect is the Dependency Array. This Dependency array decides how the useEffect behaves in response to the changes in the component props.

useEffect(() => {
console.log(`RENDER: Count: ${count}`);
},<dependency array>);

We can provide as many props in the array on which we want the useEffect() to make the component re-render. If any of the defined dependent componentsWillReceiveProps change, the component will re-render.

Now this can have different possible possible forms —

  • useEffect( () => {}) : Default state i.e. watch for every thing and re-render on every update (componentDidUpdate).
  • useEffect( () => {}, []) : No dependent props to watch for any change (componentDidMount).
  • useEffect( () => () => {console.log("OverReact!!")}, []) : Returning a function from useEffect() (componentDidUnMount).

The Affect of UseEffect

By default the useEffect() has no effect on the component i.e. it runs on every render. But playing with arguments of useEffect we could achieve what different class lifecycle methods can do in a React class without using one.

componentDidUpdate: Simply using useEffect with no dependents array allows us to achieve these lifecycle methods.

componentDidUpdate example. Check console!! | Edit in JSFiddle

componentDidMount: Typically this is required when you need to fetch some data or other operations when the component first mounts.

componentDidMount example. Check console!! | Edit in JSFiddle

componentDidUnmount: The useEffect function can return an optional cleanup function. The useEffect here returns a callback function. Here you can do the necessary cleanup like removing event handlers, reference cleanup etc.

componentDidUnmount example. Check console!! | Edit in JSFiddle

Fact: The hooks are asynchronous unlike their class counterparts. This means that they dont block the browser rendering and can happen in the background while browser is busy with its event loop. There are some hooks like useLayoutEffect which are synchronous which is obvious as the layout rendering is a blocking change the browser does when repainting happens.

Mind==Blown | Photo on Giphy

useContext API

Imagine that you have a deeply nested hierarchy of React components. Now, let’s say that the parent needs to pass data, not to its immediate child but to a grandchild or a great-grandchild. This is (in)famously called prop-drilling.

You would have to update the prop at each child component. Redundant, time-consuming, and error-prone!

It is in scenarios like these that useContext comes in handy.

Use context props drill down | Photo on Miro medium

useContext creates a “context” at the top level and allows it to be “used” anywhere in the hierarchy.

This context or object/value is available to all components in the hierarchy following that top-level component. This happens without having to pass any props down the hierarchy explicitly.

Up and running with useContext

There are three major things to remember to get using the context API

  • Import useContext hook from react lib wherever you want to create a context:

import {useContext} from React;

  • React.createContext to create a context that will be sharable amongst the components:

const ThemeContext = React.createContext();

  • Provider to provide this context down the line (child components): We will create a component <ThemeProvider>that will create a context and pass it down to its children.
ThemeProvider example

Few things to note about the bold line:

First we return JSX from this component that is wrapped in the provider. A provider is something that provides a data/value to this component and its underlying hierarchy.

If you have worked with redux stores, we wrap the entire <App> component in a provider and Provide the redux store to it so this store is now available to all the hierarchy through the reducers involved.

Similarly, here we create a context and wrap our root component in this provider and supply the value/data/object to be accessed by its children directly.

The all new <Counter> Component:

Counter component using useContext

The above piece of code grabs the theme from the context.And we use this theme as part of the jsx

The <App> Component

App component

Here is the full code for useContext:

UseContext example | Edit in JSFiddle
Thank you!! | Photo on Gyfcat

--

--

Devansh Khanwalkar
Walmart Global Tech Blog

UI Engineer at WalmartLabs, Ex OLA, JS Enthusiast, Pipe-Dreamer, Musicophile