Swizec Teller - a geek with a hatswizec.com

    Wormhole state management

    Engineers often jump into Redux, MobX, XState, or Recoil before they really need to. That's what large orgs use so it must be right.

    You are not Google, Facebook, Amazon, or Uber. You're shooting yourself in the foot by blindly following their patterns. Probably.


    This article is based on my talk at Reactive Conf Meetup #4 in May ๐Ÿ‘†

    The Wormhole State Management pattern stems from a single idea: use the least complex machinery that solves your problem.

    That's why it's a pattern, not a library.

    You have control to make it as simple or complex as fits the problem. You can scale it from small examples to large codebases.

    It's the pattern behind libraries like useAuth and every React app โ€“ big or small โ€“ that I've built in the past 18 months. And now it has a name โœŒ๏ธ

    Here's how it works

    Keep local state local


    What's the smallest amount of sharing you can get away with?

    Keep your state there. As close to where it's used as possible.

    If a single component cares, use that. If a few components care, share it with props. If many components care, put it in context.

    Context is like a wormhole. It bends your component tree so pieces far apart can touch.

    Leverage custom hooks to make this easy.

    Keep state simple


    What's the simplest state you can get away with?

    Do that. You can always change it later.

    You can always make a simple codebase complex, but you can't make a complex codebase simple.

    Your code becomes easier to understand, too.

    Imagine stepping into a component you haven't seen in 2 years. What happens?

    First you read the component code. It kind of makes sense.

    Then you travel 5 files into a different directory and learn about how its state works.

    3 more files away is a bit more of its state.

    Another 10 files away are all the side-effects from who knows what.

    Before you know it, you've read most of the codebase and you're still not sure how this component works. You try to isolate it to run some tests and realize it needs the whole state management jungle to work.

    You wanted a banana. You got a gorilla holding the banana and the entire jungle.

    gorilla_holding_banana giphy

    An example

    Follow along as I build a contrived click counter. The Wormhole State Management pattern is best explained through examples โœŒ๏ธ

    step 1

    We start with useState because it's the simplest.

    Click through for source
    Click through for source

    Click through for source
    Click through for source

    count holds the current number of clicks, setCount lets us update the value on every click.

    Simple enough.

    Presentation isn't the prettiest though. Let's improve it with a custom button component and some nesting.

    step 2

    Click through for source
    Click through for source

    We created a reusable PrettyButton that ensures every button in our app looks fabulous. Nice and pink.

    State remains in the ClickCounter component.

    Click through for source
    Click through for source

    This is the least amount of state sharing necessary. We kept state simple, too.

    The counter component cares about clicks and counts so it passes a callback into the button as a prop. Function gets called, state updates, component re-renders.

    No complex machinery required.

    step 3

    What if our state is more complex? We have 2 items that belong together.

    You can keep complex values in your state. Works great.

    Click through for source
    Click through for source

    Click through for source
    Click through for source

    We've split count into an object โ€“ { A, B }.

    Now a single piece of state can hold multiple values. Separate counts for separate button clicks.

    React uses JavaScript equality to detect changes for re-renders so you have to make copies of full state on every update. This gets slow around 10,000 or so elements.

    You can use useReducer here, too. Especially when your state becomes more complex and items often update separately.

    Similar state using useReducer would look something like this:

    Click through for source
    Click through for source

    The more complex your state, the more this makes sense.

    But I think those switch statements get messy fast and your callback functions are already actions anyway.

    step 4

    What if we want 2 buttons to update the same state?

    Click through for source
    Click through for source

    You can pass both the count and setCount to your components as props. But this is getting messy.

    Click through for source
    Click through for source

    We've created a component that's hard to move and needs to understand too much of parent logic. Concerns are split, abstractions are weird, and we've created a mess.

    You can fix it a little by passing just the parts of state it needs and a more customized setCount. But that's a lot of work.

    step 5

    Instead, you can use a wormhole to share state with a custom hook. ๐Ÿคฉ

    Click through for source
    Click through for source

    You now have 2 independent components sharing state. Put them anywhere in your codebase and it Just Worksโ„ข.

    Need to access shared state somewhere else? Add the useSharedCount hook and voila.

    Here's how this part works.

    We have a context provider with some machinery inside:

    Click through for source
    Click through for source

    The context provider uses a rich state variable to keep your state. This is { A, B } for us.

    The contextValue is a richer piece of state that also holds everything you need to manipulate that state. Often this would be a dispatch method from your reducer, or custom state setters like we have here.

    Our setSharedCount method gets a key and val and updates that part of state.

    Click through for source
    Click through for source

    We then have a side-effect that watches state for changes and triggers re-renders when needed. This avoids deep re-renders every time we redefine our dispatch methods or whatever.

    Makes the React tree more stable โœŒ๏ธ

    Every component rendered within this provider can use this same custom hook to access everything it needs.

    Click through for source
    Click through for source

    The custom hook taps into React Context for shared state, defines simpler incA and incB helper methods, and returns them with state.

    That means our AlternativeClick component can look like this:

    Click through for source
    Click through for source

    Gets count and incB from custom hook. Uses them.


    What about performance?

    It's good.

    Share state as little as possible. Use different context providers for different parts of your app.

    Don't make it global unless it needs to be global. Wrap the smallest part of your tree that you can get away with.

    Yes even though it's easier to always wrap everything.

    What about complexity?

    What complexity? Keep it small. Don't stuff shit in there that you don't need.

    That's where the foot shooting starts. When folks put everything in global state.

    Also why apps get slow ๐Ÿ˜‰

    I hate managing my own state, bleh

    That's fair. Did you know that this same pattern works with many state management libraries?

    See that part in our SharedCountProvider that deals with state changes? This part:

    Click through for source
    Click through for source

    You can use XState for that. Or a reducer. Or even Redux, if you really want to.

    Altho if you're using Redux you might as well go all the way ๐Ÿ˜›

    Does this really scale up and down all sorts of apps?

    Yes. Try it.


    Did you enjoy this article?

    Published on June 29th, 2020 in Front End, Technical

    Learned something new?
    Want to become an expert?

    Here's how it works ๐Ÿ‘‡

    Leave your email and I'll send you thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Join Swizec's Newsletter

    And get thoughtful letters ๐Ÿ’Œ on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.

    "Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. ๐Ÿ‘Œ"

    ~ Ashish Kumar

    Join over 14,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. โœŒ๏ธ

    Have a burning question that you think I can answer?ย I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

    Ready to Stop copy pasting D3 examples and create data visualizations of your own? ย Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first ๐Ÿ’ฐ on the side with ServerlessReact.Dev

    Want to brush up on your modern JavaScript syntax?ย Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you areย โค๏ธ

    Created bySwizecwith โค๏ธ