Managing UI State in a React App

Introduction

UI State is the state that is only useful for controlling the interactive parts of our React app. The other state being Server-cache(State's stored on the server, and we store in the client for quick-access: like user data).

useState is a Hook that lets you add React state to function components.

Lifting State Up

If we have 2 sibling components that both need same state(or functions/methods), we need to lift that state to the least parent component and pass the data down to the components that need through props.

Prop Drilling

Now, if some component far away in the component tree needs the state, you need to lift the state all the way to the top and pass props to all intermediate components to get the data down to the component that actually needs it.

This is what is called prop drilling- passing data from one part of the React Component tree to another by going through other parts that do not need the data but only help in passing it around.

Lets take a simple example:
cont1

Here, UserPanel component and UserPanelContent component are intermediate components acting as tunnels to get the data down to the Welcome component that actually needs the data.

Using Composition to avoid Prop Drilling

Instead of making components that render components and wiring props everywhere like this, you could compose things together through children props.

Using children prop increases the ability to compose i.e it makes the UserPanel costomizable(such that you can choose what goes inside the Panel) and also it eliminates prop drilling.

There are various situations where prop drilling can cause some real pain in the process of refactoring especially.

Context

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context.
~React docs

If 3 components require data(or methods), we choose a common parent and wrap it in a provider and then we wrap the components that require data/method in a consumer.

Taking example from the previous code:
cont2

Here, the useContext hook gives us an extra, much prettier, way to consume context.
If you have a really large application and you have the entire thing surrounded by a Context Provider anytime you make a change to that state it is going to render everything that is nested under the provider which can make interactions feel slow. Adding useMemo increases performance which says hey if these values dont change dont render these components.

Outro

Many UI state managment tools are available like Redux, Mobx, Recoil, Jotai, XState which may be used but React is all you need to manage your applications UI state most of time.

Further Reading

29