Hi there, it's Howard from Web Dev Distilled!
Welcome to the next article on my React series. π
In this article and the next one, I will talk to you about the React Mindset.
This is the Foundation of all great React developers out there.
Buckle up! and let's go.
Recipe of Professional React Applications
A professional React application requires 2 main components.
Know How to Work with React API.
Know How to THINK in React.
With that being said, we'll discuss in-depth about the second one below.
And of course, more about the first one in my upcoming articles. π
π
From an overall perspective, thinking in React will contain 2 parts.
Thinking about State Management.
Thinking about Components, Composition, and Reusability.
π Thinking in React is a whole process that can help us build applications in a more structured and robust way.
The "Thinking In React" Process
Here is the approach I took to build React or front-end applications.
Break the desired UI into components and establish the Component Tree.
Build a STATIC version in React - without any state first.
Think about the State:
When to USE STATE?
Types of State: Local or Global?
Where to place each piece of state
π Thinking in State transitions, NOT element mutations.
Establish the DATA FLOW
ONE WAY DATA FLOW (From Parent to Child Component)
Child-to-parent communication (Reverse data flow)
Accessing Global State
When you know HOW TO THINK in REACT, you will be able to answer the below questions π
1οΈβ£ How to Break up the UI design into components
2οΈβ£ How to make components Reusable?
3οΈβ£ How to assemble UI from reusable components?
4οΈβ£ What pieces of state do you need for interactivity? Where to place state, what component should own each piece of state?
5οΈβ£ Types of State should you use?
6οΈβ£ How to make data flow through the application?
Understanding State π€
This article is all about Fundamentals of State Management. Therefore, let's review some core concepts here.
If you already have a lot of experience with Frontend development in general, feel free to skip to the State Management section!
π State is the most important concept in React.
Up until this point, we all know that, props
is the Data that comes from OUTSIDE of the Component.
That's the data that the component received from its parent.
BUT WHAT IF the Component ITSELF needs to actually HOLD its own data over time? π€
WHAT IF we want to make our app INTERACTIVE? Changing UI as the result of an action?
That's where STATE comes into play! π
So here is the Definition of State.
π is the DATA that the component can HOLD over time, necessary for information that it needs to REMEMBER throughout the app's lifecycle.
π is the MEMORY of Component
π is a single variable in a component, which could be called "state variable/piece of state".
π The most important aspect of State is - Update the Component State triggers React to RE-RENDER the component.
Whenever we update a piece of state in a component, that will trigger React to Re-render the component in the User Interface.
It's gonna create a new Updated View for the Component.
π State is how React keeps the UI in sync with Data.
π State allows developers to: Update the component view (by rendering) + PERSIST local variables during renders.
π React "reacts" to State changes to update the UI accordingly.
More Understanding about Props
π is external data, owned by Parent Component
π Used to communicate data from parent-child components
π can be used to configure how child components look.
π Similar to function parameters
π It's Read-only.
π Receiving new props will trigger the component to re-render. Usually when the parent's state has been updated.
More thought about State
π Entire UI is the Representation of the current state of all components.
React application is fundamentally all about changing state over time and correctly displaying that state at all times.
π With state we view UI as a Reflection of data changing over time Instead of explicit DOM manipulation.
π We describe that reflection of data using state, event handlers and JSX.
When to use State?
π Use for any data that the component should keep track of, in other words - "Remember" over time.
This is the data will change at some point. In Vanilla JS, that's a let
variable, an []
or {}
π Whenever you want something in the component to be dynamic, go ahead and create a piece of state for that thing.
And then UPDATE the State when this "thing" change - AKA: it's Dynamic.
Example: The form Button can be disabled or active. To be more specific, it should be disabled when the form is submitting.
So we create a state variable
isSubmitting
that tracks whether the form is submitting or not.On
isSubmitting = true
we disable the Button, and vice versa.
π If you want to change the way a component looks, and data it displays, you'll need to update its state. Usually happens in an even handler function.
π For data should NOT TRIGGER component to re-renders, DON'T USE STATE.
Just use a regular variable instead. This is a very common beginner mistake.
Let's get into State Management!
If you just working with just one component, or a few components in a small application. State management will not be the problem at all.
But when your application start growing, or you're working in a team of Frontend developers, that's when things got mess up very quickly, if you don't have a solid progress or method to grow and manage all the states people and yourself created.
You started having states scattered all over places everywhere in your application.
Some people created so many redundant states that could be solved by just regular variables or with a ref
Your application's performance started getting worse, there were so many unnecessary re-renders, just because of bad state management. etc, you named it.
That's why to build professional frontend applications at scale in general or understand how to think in React, we should put State Management into the pedestal.
State management is all about Deciding
π WHEN to create pieces of state?
π WHAT TYPES of State are necessary?
π WHERE to place each piece of state?
=> Giving each piece of state a HOME.
π How the DATA FLOWS through the app.
Types of State by Accessibility
Local State
π State needed ONLY by ONE or FEW components.
π State that is defined in a component and ONLY that component and child components have access to it (by passing via props).
π We definitely should start with Local state first.
Global State
π State that MANY COMPONENTS might need.
π Shared state that is accessible to every component in the entire application.
When to create? Where to place them?
π If a piece of state is only used by this component, just leave it in the component.
π If it also needs to be used by a child component, Pass it to the child component through props.
π If a piece of state is used by a few sibling components? So lift the state up to first common parent. (more about this in the section below π)
π If it's not all of the above cases, it's probably Global State. We will get into global state management later in this article.
Types of State by Domain
REMOTE State
π All application data loaded from a REMOTE server (API)
π Usually asynchronous
π Needs re-fetching and updating
UI State
π Everything else
π Application theme, list filter, sorting, form data, etc
π Usually SYNCHRONOUS and Stored directly inside the application.
SHARING STATE WITH SIBLING COMPONENT
In React we have One Way data flow.
Data can only flow down from the Parents to the Child Component.
But CANNOT SIDEWAYS to siblings components.
Therefore, we cannot easily communicate a piece of state from ChildComponentB
to ChildComponentA
as props. That's just not possible.
So we need a way to share State with components that are sideways or further up in the Component tree.
How can we do that? π€
Let's get into Lifting State Up!
By lifting State up, we have successfully shared one piece of state with multiple components in different positions in the component tree.
π¨ That's something you will use all the time in React applications. Please get used to this pattern.
Let's talk more about the current scenario.
Here we want to share a commonState
between <ChildComponentA/>
and <ChildComponentB/>
We applied the Lifting State Up
technique by place the commonState
to the First common Parent component of <ChildComponentA/>
and <ChildComponentB/>
and pass commonState
down to 2 child components as props.
Yay! π We solved the first problem.
BUT, if the data can only flows from Parent to Children. HowChildComponentA
(Child) can update state in theParentComponent
(Parent)?
It turns out, the solution is quite simple. π
Here is the "Child to Parent Communication"
AKA "Inverse Data Flow"
Child-to-parent communication means the child component updating parent state (the data "flowing" up the component tree).
Here we passed the setCommonState
down to any component that needs to update the state.
So now any time inside the <ChildComponentB />
we can use setCommonState
to update the commonState
in the <ParentComponent/>
Deriving State
It sounds complicated, but the definition is quite straightforward π
Derived State: is State that is Computed from an existing piece of state or from props.
Here we have an example:
const [cart, setCart] = useState([
{ name: "iPhone 10", price: 200 },
{ name: "Apple Watch", price: 150 },
]);
const [totalItems, setTotalItems] = useState(2);
const [totalPrice, setTotalPrice] = useState(350);
π Here we have three separate pieces of state, even though totalItems
and totalPrice
depend on cart
π There is no need to do so, it's quite problematic.
Because we need to keep them updated together (in sync)
Whenever we update the cart
we have to manually update the totalItems
and totalPrice
otherwise, our states will get out of sync.
The second problem is that all of the information about the total items, and total price is already included inside cart
state, we can easily compute them from there.
π That will cause another problem, 3 state updates will cause 3 re-renders.
π Here is the perfect time to apply Deriving State
const [cart, setCart] = useState([
{ name: "iPhone 10", price: 200 },
{ name: "Apple Watch", price: 150 },
]);
// π’ β
const totalItems = cart.length;
const totalPrice = cart.reduce((acc, cur) => acc + cur, 0);
π Just use regular variables, no need useState
π cart
state is the single source of truth for all related data
π Works because re-rendering component will automatically re-calculate all the derived state.
Of course, we cannot derive state for most cases. But whenever you have a situation like the before example, where one state can easily be computed from another piece of state or props.
You should ALWAYS prefer derived state.
State Placement Options
Where to place state? | Available Tools | When to use? |
Local Component | useState, useReducer, or useRef | Local State |
Parent Component | useState, useReducer , or useRef | Lifting up State |
Context | ContextAPI, useState or useReducer | Global UI State (preferably) |
3rd-party library | Redux, Zustand, SWR, React Query, etc | Global State (Remote server state or UI) |
URL | React Router | Global State, passing state between pages |
Browser | LocalStorage, session storage, etc | Storing data in user's browser. |
That's a very long article, but I already took a lot of my time to reflect, distill everything I know, and learn from great teachers and resources out there.
I hope you will gain some helpful knowledge here.
Thanks for reading.
Have a great day! π
Reference resources: Jonas Schmedtmann & React Documentation.