Before I can set you loose on the world, we should talk about managing state. It's where most engineers shoot themselves in the foot.
You'll notice you shot yourself in the foot six months ago when all of a sudden it becomes near impossible to build new features, add functionality, and know what's going on with your app. If you find yourself spending a lot of time confused about why your UI does something, you've shot yourself in the foot.
Don't worry tho, happens to everyone!
I shoot myself in the foot all the time. You can't predict how your app is going to evolve. You can't know how technology is going to improve. Can't know how your team will grow.
Best approach is to optimize for change.
Otherwise you might have to do a rewrite. Rewrites are bad. One of the most infamous rewrite story is about The Rewrite that Killed Netscape. You might not even have heard of Netscape 😉
Let's save you from that.
We're using a unidirectional data flow architecture with a single source of truth. That means you always know what to expect. Think of your app as a giant circle.
Data goes from your source of truth into your components. Events go from your components into your source of truth. All in one direction
Our main App component holds state for your entire application. Anything that multiple components should be aware of lives here. This state flows down the hierarchy via props. Changes happen via callbacks, also passed down through props.
Like this 👇
- The Main Component –
App– holds the truth
- Truth flows down through props
- Child components react to user events
- They announce changes using callbacks
- The Main Component updates its truth
- Updates flow back down the chain
- UI updates through re-renders
This looks roundabout, but it's amazing. Far better than worrying about parts of the UI growing out of sync with the rest of your app. I could talk your ear off with debugging horror stories, but I'm nice, so I won't.
When a user clicks one of our controls, a
Toggle, it invokes a callback. This
in turn invokes a callback on
ControlRow, which invokes a callback on
Controls, which invokes a callback on
With each hop, the nature of our callback changes.
which entry was toggled,
Controls how to update the data
filter function, and
App a composite filter built from all
the controls. You'll see how that works in the next chapter.
All you have to remember right now is that callbacks evolve from passing low-level information to high-level business logic. Starts with "I was clicked" ends with "Update visualization filter"
When the final callback is invoked,
App updates its repository of truth –
this.state – and communicates the change back down the chain via props. No
additional wiring needed on your part. React's got you covered.
You can think of it like calling functions with new arguments. Because the functions – components – render the UI, your interface updates.
Because your components are well-made and rely on their props to render, React's engine can optimize these changes. It compares the new and old component trees and decides which components to re-render and which to leave alone.
Functional programming for HTML! :sunglasses:
The functional programming concepts we're relying on are called referential transparency, idempotent functions, and functional purity. I suggest Googling them if you want to learn the theory behind it all.
You may have heard of React Context, Redux, MobX and other state handling libraries. They're all great in different ways and the internet can't decide which one is best. Everyone has their own little twist on the story.
And yet the basic principles are all the same:
- Single source of truth
- State flows down
- Updates flow up
Where React Context, Redux, MobX and other libraries help, is how much work it is to build this machinery and keep it running. How much flexibility you get when moving components. Basically how easy it is to change your app later.
Remember the rewrite conundrum?
Our basic approach binds business structure to UI structure. Your state, your props, your callbacks, they all follow the same hierarchy as your UI does.
Want to move the buy button? Great! You have to update the entire chain of components leading from your state to that button.
Everything needs new callbacks and new props.
This is known as prop drilling and fast becomes super tedious. Rewiring your whole app just to move a single button is no fun.
To solve this problem, React Context, Redux, MobX, etc. decouple your business logic from your UI architecture. Take state out of the main component and move it into its own object. Connect everything to that instead.
Now it doesn't matter where you move that button. It still triggers the same function on the same state. Every other component that cares about that state updates too.
Different libraries have different details for how that works, but they all follow the same idea and solve the same problem.
We're sticking with the basic approach because it's easier to explain, works without additional libraries, and is Good Enough™.