How to build bulletproof react components
Tags: react, javascript, webdev
Published: 2020-04-12 11:08:45

## Introduction


React is a *declarative* framework. This means instead of describing what you need to change to get to the next state (which would be *imperative*), you just describe what the dom looks like for each possible state and let react figure out how to transition between the states. 

Shifting from an imperative to a declarative mindset is quite hard and often times when I spot bugs or inefficiencies in code it's because the user is still stuck in an imperative mindset. 
In this blog post I' ll try to dive deep into the declarative mindset and how you can use it to build unbreakable components.

## Imperative vs Declarative:

check out this example:
{% codesandbox imperative-toggle-5vx4p %}

Every time you click the button the value toggles between `true` and `false`. If we were to write this in an **imperative** way it would look like this:

```js
toggle.addEventListener("click", () => {
  toggleState = !toggleState;
  // I have to manually update the dom 
  toggle.innerText = `toggle is ${toggleState}`;
});
```
*Full example [here](https://codesandbox.io/s/imperative-toggle-5vx4p?file=/src/index.js:194-373)*


And here is the same thing written in **declarative code**:
```jsx
  const [toggle, setToggle] = useState(false);
  // notice how I never explicitely have to update anything in the dom
  return (
    <button onClick={() => setToggle(!toggle)}>
      toggle is {toggle.toString()}
    </button>
  );
```
*full example [here](https://codesandbox.io/s/declarative-toggle-mi29h)*


Every time you want to change the `isToggled` value in the first example you have to remember to update the dom as well, which quickly leads to bugs. In React, your code "just works".

## The Mindset

The core of your new mindset should be this quote:

> Your view should be expressed as a pure function of your application state.

or,

> view = f(application_state)

or,


![view = f(application_state)](https://dev-to-uploads.s3.amazonaws.com/i/7zeu2xsybt6zuk8ublia.png)

*your data goes through a function and your view comes out the other end*

React's function components align much closer to this mental model than their old class components. 
 
This is a bit abstract so let's apply it to our toggle component from above:

> the "toggle is" button should be expressed as a pure function of the `isToggled` variable.

or

> button = f(isToggled)

or

![visual explanation](https://dev-to-uploads.s3.amazonaws.com/i/sthccnpx6z85xmh2d5mj.png)

*(I'll stick to the mathematical notation from now on but they're basically interchangeable)*

Let's extend this example. Say whenever `isToggled` is `true` I want the button to be green, otherwise, it should be red.

A common beginner mistake would be to write something like this:

```jsx
const [isToggled, setIsToggled] = useState(false);
const [color, setColor] = useState('green');

function handleClick(){
  setIsToggled(!toggle)
  setColor(toggle ? 'green' : 'red')
}

  return (
    <button style={{color}} onClick={handleClick}>
      toggle is {isToggled.toString()}
    </button>
  );
```

If we write this in our mathematical notation we get

> button = f(isToggled, color)

right now our `application_state` is made out of `isToggled` and `color`, but if we look closely we can see that `color` can be expressed as a function of `isToggled`

> color = f(isToggled)

or as actual code

```
const color = isToggled ? 'green' : 'red'
```

This type of variable is often referred to as `derived state` (since `color` was "derived" from `isToggled`)

In the end this means our component still looks like this:

> button = f(isToggled)


## How to take advantage of this in the real world

In the example above it was quite easy to spot the duplicate state, even without writing it out in our mathematical notation, but as our apps grow more and more complex, it gets harder to keep track of all your application state and duplicates start popping up. 
A common symptom of this is a lot of rerenders and stale values.

Whenever you see a complex piece of logic, take a few seconds to think about all the possible pieces of state you have. 

![Illustration of state in ui](https://dev-to-uploads.s3.amazonaws.com/i/zscgodw8q8ytjw60wzk5.png)

> dropdown = f(selectedValue, arrowDirection, isOpen, options, placeholder)

then you can quickly sort out unnecessary state

> arrowDirection = f(isOpen) -> arrowDirection can be derived

You can also sort what state will be in the component and what will come in as props. `isOpen` for example usually doesn't need to be accessed from the outside of a dropdown.
From that we can tell that our component's api is probably going to look like this: `<dropdown options={[item1, item2]} selectedValue={null} placeholder='Favorite food' />`. 

Writing the component now will be incredibly easy since you already know exactly how it's going to be structured. All you need to do now figure out is how to render your state to the dom.

## One more example


![pagination](https://dev-to-uploads.s3.amazonaws.com/i/obxotadbml3hk554tmjs.png)

This looks like a lot of state at first glance, but if we look closely we can see that most of them can be derived:

`isDisabled = f(selectedValue, range)`
`"..." position = f(selectedValue, range)`
`middle fields = f(selectedValue, range)`
`amount of fields = f(selectedValue, range)`

So what remains, in the end, is just

> pagination = f(selectedValue, range)

here's my implementation: 
{% codesandbox pagination-z2951 %}

It's robust, fast and relatively easy to read. 

Let's take it one step further and change the route to `/${pageNumber}` whenever the pagination updates.

Your answer may look somewhat like this:
```jsx
const history = useHistory();
const [page, setPage] = useState(1);

function handleChange(newPage){
  setPage(newPage)
   history.push(`/${newPage}`);
}

useEffect(()=>{
  setPage(history.location.pathname.replace("/", ""))
},[])

  return (
    <div className="App">
      <Pagination value={page} range={12} onChange={handleChange} />
    </div>
  );
```

If it does, then I have some bad news: you have duplicate state. 

> pageNumber = f(window.href)

pageNumber doesn't need its own state, instead, the state is stored in the url. [here](https://codesandbox.io/s/pagination-with-router-gdqjy?file=/src/App.js:234-276) is an implementation of that.


## Other implications

Another big implication of our new mindset is that you should stop thinking in lifecycles.
Since your component is just a function that takes in some state and returns a view it doesn't matter when, where and how your component is called, mounted or updated. Given the same input, it should always return the same output. This is what it means for a component to be **pure**. 
That's one of the reasons why hooks only have `useEffect` instead of `componentDidMount` / `componentDidUpdate`. 


Your side effects should also always follow this data flow. Say you want to update your database every time your user changes the page, you could do something like this:
```js
 function handleChange(newPage) {
    history.push(`/${newPage}`);
    updateDatabase(newPage)
  }
```
but really you don't want to update your database whenever the user clicks, you want to update your database whenever the value changes. 
```js
useEffect(()=>{
  updateDatabase(newPage)
})
```
Just like your view, your side effects should also be a function of your state.

## Going even deeper

There are a couple of exceptions to this rule in react right now, a significant one is data fetching. Think about how we usually fetch data:

```jsx
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(false)

useEffect(()=>{
 setIsLoading(true)

  fetch(something)
   .then(res => res.json())
   .then(res => {
     setData(res)
     setIsLoading(false)
    })
},[])

return <div>{data ? <DataComponent data={data} /> : 'loading...'}</div>
```

There is a ton of duplicate state here, both `isLoading` and `data` just depend on whether our fetch promise has been resolved. 
We need to do it this way right now because React can't resolve promises yet.
 
[Svelte](https://svelte.dev/docs#await) solves it like this:
```svelte
{#await promise}
	<!-- promise is pending -->
	<p>waiting for the promise to resolve...</p>
{:then value}
	<!-- promise was fulfilled -->
	<p>The value is {value}</p>
{:catch error}
	<!-- promise was rejected -->
	<p>Something went wrong: {error.message}</p>
{/await}
```

React is working on something similar with [suspense for data fetching](https://reactjs.org/docs/concurrent-mode-suspense.html#what-is-suspense-exactly)


Another big point is animation. Right now, updating state at 60fps is often not possible. A great library that solves that in a declarative way is [react spring](https://www.react-spring.io/). [Svelte again has a native solution for this](https://svelte.dev/docs#animate_fn)  and I wouldn't be surprised if that's something else react will look at in the future.


## Final thoughts

whenever
 - your app rerenders often for no real reason
 - you have to manually keep things in sync
 - you have issues with stale values
 - you don't know how to structure complex logic

take a step back, look at your code and repeat in your head:
> Your view should be expressed as a pure function of your application state.



Thanks for reading ❤

If you didn't have that "aha-moment" yet I recommend building out the pagination or any component that you can think of and follow exactly the steps outlined above.

If you want to dive deeper into the topic I recommend these 2 posts:
 - https://medium.com/@mweststrate/pure-rendering-in-the-light-of-time-and-state-4b537d8d40b1
 - https://rauchg.com/2015/pure-ui/

If you think there is something I could make clearer or have any questions/remarks feel free to [tweet at me](https://twitter.com/JescoWuester) or just leave a comment here.