Swizec Teller - a geek with a hatswizec.com

    Swap useReducer with XState – CodeWithSwiz 13

    In Refactoring a useReducer to XState, pt1 we talked about a 5 step process to turn a reducer into an XState state machine. Today answers "Ok now what?"

    CodeWithSwiz is a twice-a-week live show. Like a podcast with video and fun hacking. Focused on experiments. Join live Wednesdays and Sundays

    useAuth relies on the Wormhole state management principle. Heck, that's where I first figured it out!

    This has made lots of people very confused. Me included. Come back 6 months later trying to fix a bug and why is there a dispatch({ type: 'startAuthenticating' }) in 5 different places wtf??

    "Don't invent new state management schemes, got it" right? No. Don't make your core state messy and confusing πŸ˜‰

    A primer on how useAuth makes wormholes

    Wormhole state management gives you global state without the overhead. You add useAuth to your component and magic happens.

    Swizec Teller published ServerlessHandbook.dev avatarSwizec Teller published ServerlessHandbook.dev@Swizec
    Omg I just opensourced the simplest way to add authentication to your React app.

    Handles everything for you. Users, login forms, redirects, sharing state between components. Everything

    πŸ‘‰

    github πŸ‘‰

    try πŸ‘‰
    Tweet media

    Internally useAuth connects to a react context holding your authentication state. Info about the user, their current state, faux cookies, and returns helper methods.

    How useAuth's wormhole state works
    How useAuth's wormhole state works

    That happens in a couple steps:

    // src/AuthProvider.tsx
    export const AuthContext = createContext<AuthContextState>({
    state: getDefaultState(),
    dispatch: () => {},
    auth0: null,
    callback_domain: "http://localhost:8000",
    customPropertyNamespace: "http://localhost:8000",
    navigate: (path: string) => {},
    })

    This creates a new context with default values. state is the application state we're managing, dispatch is the changer method, the rest is meta data and configuration. Values we need everywhere that don't change.

    // src/AuthReducer.tsx
    // Holds authentication state
    const [state, dispatch] = useReducer<React.Reducer<AuthState, AuthAction>>(
    authReducer,
    getDefaultState()
    )
    const [contextValue, setContextValue] = useState<AuthContextState>({
    state,
    dispatch,
    auth0,
    callback_domain: callbackDomain,
    customPropertyNamespace,
    navigate,
    })
    // Update context value and trigger re-render
    // This patterns avoids unnecessary deep renders
    // https://reactjs.org/docs/context.html#caveats
    useEffect(() => {
    setContextValue((contextValue: AuthContextState) => ({
    ...contextValue,
    state,
    }))
    }, [state])

    The provider instantiates a new reducer and gets access to current state and a dispatch method. These and the config metadata are shoved into a new useState.

    We use an effect to update the context value when reducer state changes. This ensures re-renders happen only when the reducer makes a change.

    useAuth wormholes into this state with a useContext.

    // src/useAuth
    export const useAuth: useAuthInterface = () => {
    const {
    state,
    dispatch,
    auth0,
    callback_domain,
    navigate,
    customPropertyNamespace
    } = useContext(AuthContext);

    Anything inside useAuth now has access to these values. Helper methods can check state, dispatch reducer actions, and look at the meta values.

    Works great.

    How XState helps us improve

    Wormhole state worked great but the reducer was problematic. Convoluted to understand and difficult to use.

    startAuthenticating fires twice in the code, so does stopAuthenticating. Why? Both where it doesn't make sense.

    An XState state machine makes the flow clearer πŸ‘‡

    useAuth state machine
    useAuth state machine

    You start as unauthenticated and trigger LOGIN. That moves you to authenticating. From there you can get an ERROR, which puts you in the error state. Or you get AUTHENTICATED and move into the authenticated state.

    LOGOUT moves you back to unauthenticated.

    Makes sense eh?

    While refactoring, we made the state machine closely follow the reducer implementation.

    Means we can do a swap!

    Change useReducer to useMachine from XState

    // src/AuthProvider.tsx
    // Holds authentication state
    const [state, dispatch] = useReducer<React.Reducer<AuthState, AuthAction>>(
    authReducer,
    getDefaultState()
    );
    πŸ‘‡
    // Holds authentication state
    const [state, send] = useMachine(authMachine);

    Change what goes into context:

    const [contextValue, setContextValue] = useState<AuthContextState>({
    state,
    dispatch,
    πŸ‘‡
    const [contextValue, setContextValue] = useState<AuthContextState>({
    state: state.context,
    send,

    Dispatch becomes send and state becomes the XState context. Yes we're overloading terms and that's unfortunate. State machine context is where we keep our application state.

    Make the same state fix in the effect:

    useEffect(() => {
    setContextValue((contextValue: AuthContextState) => ({
    ...contextValue,
    state
    }));
    }, [state]);
    πŸ‘‡
    useEffect(() => {
    setContextValue((contextValue: AuthContextState) => ({
    ...contextValue,
    state: state.context
    }));
    }, [state]);

    And then it's a schlep to go around the codebase and change every dispatch({ type: X }) to send(X).

    We went from 9 dispatch() calls to 9 send() calls, which doesn't sound like an improvement. πŸ€”

    But it's because state machines are strict and we used 2 sends to hydrate from local storage. useReducer let us fake that part.

    πŸ˜…

    Next step

    David, the creator of XState, says we can stop using a context provider. And that's what we're gonna try next.

    Cheers,
    ~Swizec

    Did you enjoy this article?

    Published on October 22nd, 2020 in CodeWithSwiz, Technical, XState, Livecoding

    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 ❀️