React 16.6.0 Goodies

React 16.6.0 was released October 2018 and with it came goodies that spice up the way we can develop with React. We’re going to cover what I consider the best of those new goodies with examples of how we can put them to use in our work.

React.memo() avoids unnecessary re-rendering

There are situations where a component re-renders, even if neither its state nor its props changed. That adds up and can be an expensive operation.

Here’s an example of a counter to show what we’re talking about:

See the Pen
React counter w/o React.memo()
by CSS-Tricks (@css-tricks)
on CodePen.

We have a child component that receives a specific value as props that do not change.

const Child = props => { console.log("rendered"); return <React.Fragment>{props.name}</React.Fragment>;
}

The child’s value is determined by the state of the App component. It’s state doesn’t change. It’s props remain the same.

class App extends React.Component { state = { count: 1, name: "Jioke" }; handleClick = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <React.Fragment> <Child name={this.state.name} /> <div>{this.state.count}</div> <button onClick={this.handleClick}>+</button> </React.Fragment> ); }
}

Yet, each button click results in two things happening: the value of count is incremented and the child component is re-rendered. Just watch:

We could resolve this with a class component using the shouldComponentUpdate() lifecycle hook, which would look like this:

class Child extends React.Component { // No re-render, please! shouldComponentUpdate(nextProps, nextState) { return nextProps.name != this.props.name } render() { console.log('rendered') return <React.Fragment>{this.props.name}</React.Fragment> }
}

That’s where React.memo() comes into play. It’s a higher-order component we can wrap around the child and, presto, now the child is shielded from unnecessary additional rendering.

const Child = React.memo(props => { console.log("rendered"); return <React.Fragment>{props.name}</React.Fragment>;
});

See the Pen
React.memo 2
by CSS-Tricks (@css-tricks)
on CodePen.

React.lazy() makes importing files a breeze while Suspense provides a fallback UI

Code splitting is crucial in web development—it enables us to import only the files we, which is not only reduces an application’s initial load, but is a core principle of the React framework.

Well, React now enables code splitting using React.lazy() and suspense right at the component level.

By default, if making use of a component (even if its usage depends on a condition), then we import it into the file where you will be using it. React.lazy() can now handle the importation like this:

const MyCounter = lazy(() => import("./Counter"));

This single line returns a promise that resolves to the imported component. From here, we can use the component as we normally would.

const App = () => ( <div> <MyCounter /> </div>
);

There are cases where we might want to render a fallback UI before the component is ready to render. For example, it might take a moment for an API call to fetch and return data. This is a great opportunity to show a loading state while the user waits. Suspense can do just that.

// Using React.lazy() to import the Counter component
const MyCounter = lazy(() => import("./Counter"));
const App = () => ( <div> // Using Suspense to render a loading state while we wait for the Counter <Suspense fallback={<div>Loading...</div>}> <MyCounter /> </Suspense> </div>
);

Suspense’s fallback prop can accept a React element, so go nuts. It can be used to display whatever fallback UI we want while the component loads.

contextType accesses provider context and passes state without render props

The Context API made it possible to share state among multiple components without having to make use of a third-party library.

Well, React 16.6 makes it possible to declare contextType in a component to access the context from a provider. This saves us from having to make use of render props to pass down context to the consumer.

See the Pen
React contextType
by CSS-Tricks (@css-tricks)
on CodePen.

First, let’s create our context:

const UserContext = React.createContext({}); const UserProvider = UserContext.Provider;
const UserConsumer = UserContext.Consumer;

We’ll make use of the provider in the App component:

class App extends React.Component { state = { input: "", name: 'John Doe' }; handleInputChange = event => { event.preventDefault(); this.setState({ input: event.target.value }); }; handleSubmit = event => { event.preventDefault(); this.setState({ name: this.state.input, input: '' }) }; render() { return ( <div> <UserProvider value={{ state: this.state, actions: { handleSubmit: this.handleSubmit, handleInputChange: this.handleInputChange } }} > <User /> </UserProvider> </div> ); }
}

The provider passes the state and the methods to consumer components that will make use of them via the value prop. To access the context, we’ll make use of this.context instead of making render props like we normally would.

class User extends React.Component { static contextType = UserContext; render() { const { state, actions } = this.context; return ( <div> <div> <h2>Hello, {state.name}!</h2> </div> <div> <div> <input type="text" value={state.input} placeholder="Name" onChange={actions.handleInputChange} /> </div> <div> <button onClick={actions.handleSubmit}>Submit</button> </div> </div> </div> ); }
}

We set static contextType to UserContext which we created earlier. With that, we are able to extract the context which includes the state and methods from this.context. We make use of ES6 destructuring to get the values so we can make use of them in the User component, which is the consumer. This looks so much cleaner and is easier to read compared to doing this with render props.

getDerivedStateFromErrors()

We have error boundary to handle errors, which makes use of componentDidCatch() and that gets fired after the DOM has been updated. It’s well suited for error reporting. But now we have getDerivedStateFromErrors() to render a fallback UI before the render completes if an error is caught. Sort of the same concept as Suspense, but for error states instead of loading states.

See the Pen
React getDerivedStateFromError
by CSS-Tricks (@css-tricks)
on CodePen.

Let’s create our error boundary component to capture the moment something goes awry:

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } // If hasError is true, then trigger the fallback UI static getDerivedStateFromError(error) { return { hasError: true }; } // The fallback UI render() { if (this.state.hasError) { return ( <h1>Oops, something went wrong :(</h1> ); } return this.props.children; }
}

We make use of getDerivedStateFromError() to spot that an error was caught by the error boundary and then return hasError as true when an error occurs. When this happens, we want to display a message to inform the user that an error has encountered.

class Counter extends React.Component { state = { count: 1 } handleClick = () => { this.setState({ count: this.state.count + 1 }) } // If the count is greater than 5, throw an error render() { if (this.state.count > 5) { throw new Error('Error') } return ( <div> <h2>{this.state.count}</h2> <button onClick={this.handleClick}>+</button> </div> ) }
}

That’s going to trigger an error when the value of count is greater than five. Next, we need to wrap our Counter component as a child of ErrorBoundary component to apply the error conditions to the component:

const App = () => ( <div> // Wrap the component in the ErrorBoundary to attach the error conditions and UI <ErrorBoundary> <Counter /> </ErrorBoundary> </div>
)

We can even limit the error to the specific piece that is broken. So, for example, let’s take a listing of locations. Instead swapping the entire list of locations for the error UI, we can slap it at the specific location where the error happened.

See the Pen
React getDerivedStateFromError 1
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Pretty nice, right?

React continues to add a bunch of useful features while making it easier to write code with each release and v16.6 is no exception. If you’ve already started using any of the latest goodies that shipped in this release, please let me—I’d be interested in seeing how you’re using them in a real project.

More Information

The post React 16.6.0 Goodies appeared first on CSS-Tricks.

Finite State Machines with React

As JavaScript applications on the web have grown more complex, so too has the complexity of dealing with state in those applications — state being the aggregate of all the data that an application needs to perform its function. Over the last several years, there has been a ton of great innovation in the realm of state management through tools like Redux, MobX, and Vuex. Something that hasn’t gotten quite as much attention, though, is state design.

What in the heck do I mean by state design?

Let’s set the scene a little bit. In the past, when building an application that needs to fetch some data from a backend service and display it to the user, I’ve designed my state to use boolean flags for various things like isLoading, isSuccess, isError, and so on down the line. As this number of boolean flags grows, though, the number of possible states that my application can have grows exponentially with it — significantly increasing the likelihood of a user encountering an unintentional or error state.

To solve this issue, I’ve spent the last several months exploring the use of finite state machines as a way to better design the state of my applications.

Finite state machines are a mathematical model of computation, initially developed in the early 1940s, that have been used for decades to build both hardware and software for a wide array of technologies.

A finite state machine can be defined as any abstract machine that exists in exactly one of a finite number of states at a given time. In more practical terms, though, a state machine is characterized by a list of states with each state defining a finite, deterministic set of states that can be transitioned to by a given action.

Due to this finite and deterministic nature, we can use state diagrams to visualize our application — before or after it’s been built.

For example, if we wanted to visualize an authentication workflow, we could have three overarching states that our application could be in for a user: logged in, logged out, or loading.

A state diagram showing an application going from logged out, to loading, to logged in.

State machines, owing to their predictability, are especially popular in applications where reliability is critical — such as aviation software, manufacturing, and even the NASA Space Launch System. They’ve been a mainstay in the game development community for decades, as well.

In this article, we’ll tackle building something that most applications on the web use: authentication. We’ll use the state diagram above to guide us.

Before we get started, though, let’s familiarize ourselves with some of the libraries and APIs we’ll be using to build this application.

React Context API

React 16.3 introduced a new, stable version of the Context API. If you’ve worked much with React in the past, you may be familiar with how data is passed from parent to child through props. When you have certain data that is needed by a variety of components, you can end up doing what’s known as prop drilling — passing data through multiple levels of the component tree to get the data to a component that needs it.

Context helps alleviate the pain of prop drilling by providing a way to share data between components without having to explicitly pass that data through the component tree, making it perfect for storing authentication data.

When we create context, we get a Provider and Consumer pair. The provider will act as the “smart,” stateful component that contains our state machine definition, and maintains a record of the current state of our application.

xstate

xstate is a JavaScript library for functional, stateless finite state machines and statecharts — it will provide us a nice, clean API for managing definitions and transitions through our states.

A stateless finite state machine library might sound a bit strange, but essentially what it means is that xstate only cares about the state and transition that you pass it — meaning it’s up to your application to keep track of its own current state.

xstate has a lot of features worth mentioning that we won’t cover much in this article (since we’ll only begin to scratch the surface on statecharts): hierarchical machines, parallel machines, history states, and guards, just to name a few.

The approach

So, now that we’ve had a little bit of an introduction to both Context and xstate, let’s talk about the approach we’ll be taking.

We’ll start by defining the context for our application, then creating a stateful <App /> component (our provider) that will contain our authentication state machine, along with information about the current user and a method for the user to logout.

To set the stage a bit, let’s take a quick look at a CodePen demo of what we’ll be building.

See the Pen Authentication state machine example by Jon Bellah (@jonbellah) on CodePen.

So, without further ado, let’s dig into some code!

Defining our context

The first thing we need to do is define our application context and set it up with some default values. Default values in context are helpful for allowing us to test components in isolation, since the default values are only used if there is no matching provider.

For our application, we’re going to set up a few defaults: authState which will be the authentication state of the current user, an object called user which will contain data about our user if they’re authenticated, then a logout() method that can be called anywhere in the app if the user is authenticated.

const Auth = React.createContext({ authState: 'login', logout: () => {}, user: {},
});

Defining our machine

When we think about how authentication behaves in an application, in its simplest form, there are three primary states: logged out, logged in, and loading. These are the three states we diagramed earlier.

Looking back at that state diagram, our machine consists of those same three states: logged out, logged in, and loading. We also have four different action types that can be fired: SUBMIT, SUCCESS, FAIL, and LOGOUT.

We can model that behavior in code like so:

const appMachine = Machine({ initial: 'loggedOut', states: { loggedOut: { onEntry: ['error'], on: { SUBMIT: 'loading', }, }, loading: { on: { SUCCESS: 'loggedIn', FAIL: 'loggedOut', }, }, loggedIn: { onEntry: ['setUser'], onExit: ['unsetUser'], on: { LOGOUT: 'loggedOut', }, }, },
});

So, we just expressed the diagram from earlier in code, but are you ready for me to let you in on a little secret? That diagram was generated from this code using David Khourshid’s xviz library — which can be used to visually explore the actual code that powers your state machines.

If you’re interested in diving deeper into complex user interfaces using finite state machines, David Khourshid has a related article here on CSS-Tricks worth checking out.

This can be an incredibly powerful tool when trying to debug problematic states in your application.

Referring back to the code above now, we define our initial application state — which we’re calling loggedOut since we’ll want to show the login screen on an initial visit.

Note that in a typical application, you’d probably want to start from the loading state and determine if the user was previously authenticated… but since we’re faking the login process, we’re starting from the logged out state.

In the states object, we define each of our states along with the corresponding actions and transitions for each of those states. Then we pass all that as an object to the Machine() function, which is imported from xstate.

Along with our loggedOut and loggedIn states, we’ve defined some actions that we want to fire when our application enters or exits those states. We’ll look at what those actions do in a bit.

This is our state machine.

To break things down one more time, let’s look at the loggedOut: { on: { SUBMIT: 'loading'} } line . This means that if our application is in the loggedOut state and we call our transition function with an action of SUBMIT, our application will always transition from the loggedOut state to the loading state. We can make that transition by calling appMachine.transition('loggedOut', 'SUBMIT').

From there, the loading state will either move the user along as an authenticated user or send them back to the login screen and display an error message.

Creating our context provider

The context provider will be the component that sits at the top level of our application and houses all the data related to an authenticated — or unauthenticated — user.

Working in the same file as our state machine definition, let’s create an <App /> component and set it up with everything we’ll need. Don’t worry, we’ll cover what each method does in just a moment.

class App extends React.Component { constructor(props) { super(props); this.state = { authState: appMachine.initialState.value, error: '', logout: e => this.logout(e), user: {}, }; } transition(event) { const nextAuthState = appMachine.transition(this.state.authState, event.type); const nextState = nextAuthState.actions.reduce( (state, action) => this.command(action, event) || state, undefined, ); this.setState({ authState: nextAuthState.value, ...nextState, }); } command(action, event) { switch (action) { case 'setUser': if (event.username) { return { user: { name: event.username } }; } break; case 'unsetUser': return { user: {}, }; case 'error': if (event.error) { return { error: event.error, }; } break; default: break; } } logout(e) { e.preventDefault(); this.transition({ type: 'LOGOUT' }); } render() { return ( <Auth.Provider value={this.state}> <div className="w5"> <div className="mb2">{this.state.error}</div> {this.state.authState === 'loggedIn' ? ( <Dashboard /> ) : ( <Login transition={event => this.transition(event)} /> )} </div> </Auth.Provider> ); }
}

Whew, that was a lot of code! Let’s break it down into manageable chunks by taking a look at each method of this class individually.

In the constructor(), we’re setting our component state to the initial state of our appMachine, as well as setting our logout function in state, so that it can be passed through our application context to any consumer that needs it.

In the transition() method, we’re doing a few important things. First, we’re passing our current application state and the event type or action to xstate, so we can determine our next state. Then, in nextState, we take any actions associated with that next state (which will be one of our onEntry or onExit actions) and run them through the command() method — then we take all of the results and set our new application state.

In the command() method, we have a switch statement that returns an object — depending on the action type — which we use to pass data into our application state. This way, once a user has authenticated, we can set relevant details about that user — username, email, id, etc. — into our context, making it available to any of our consumer components.

Finally, in our render() method, we’re actually defining our provider component and then passing all of our current state through the value props, which makes the state available to all of the components beneath it in the component tree. Then, depending on the state of our application, we’re rendering either the dashboard or the login form for the user.

In this case, we have a pretty flat component tree beneath our provider (Auth.Provider), but remember that context allows that value to be available to any component beneath our provider in the component tree, regardless of depth. So, for example, if we have a component nested three or four levels down and we want to display the current user name, we can just grab that out of context, rather than drilling it all the way down to that one component.

Creating context consumers

Now, let’s create some components that consume our application context. From these components, we can do all sorts of things.

We can start by building a login component for our application.

class Login extends Component { constructor(props) { super(props); this.state = { yourName: '', } this.handleInput = this.handleInput.bind(this); } handleInput(e) { this.setState({ yourName: e.target.value, }); } login(e) { e.preventDefault(); this.props.transition({ type: 'SUBMIT' }); setTimeout(() => { if (this.state.yourName) { return this.props.transition({ type: 'SUCCESS', username: this.state.yourName, }, () => { this.setState({ username: '' }); }); } return this.props.transition({ type: 'FAIL', error: 'Uh oh, you must enter your name!', }); }, 2000); } render() { return ( <Auth.Consumer> {({ authState }) => ( <form onSubmit={e => this.login(e)}> <label htmlFor="yourName"> <span>Your name</span> <input id="yourName" name="yourName" type="text" value={this.state.yourName} onChange={this.handleInput} /> </label> <input type="submit" value={authState === 'loading' ? 'Logging in...' : 'Login' } disabled={authState === 'loading' ? true : false} /> </form> )} </Auth.Consumer> ); }
}

Oh my! That was another big chunk of code, so let’s walk through each method again.

In the constructor(), we’re declaring our default state and binding the handleInput() method so that it references the proper this internally.

In handleInput(), we’re taking the value of our form field from our render() method and setting that value in state — this is referred to as a controlled form.

The login() method is where you would normally place your authentication logic. In the case of this app, we’re just faking a delay with setTimeout() and either authenticating the user — if they’ve provided a name — or returning an error if the field was left empty. Note that the transition() function that it calls is actually the one we defined in our <App /> component, which has been passed down via props.

Finally, our render() method displays our login form, but notice that the <Login /> component is also a context consumer. We’re using the authState context to determine whether or not to show our login button in a disabled, loading state.

Using context from deep in the component tree

Now that we’ve handled the creation of our state machine and a way for users to login to our application, we can now rely on having information about that user within any component nested under our <Dashboard /> component — since it will only ever be rendered if the user is logged in.

So let’s create a stateless component that grabs the username of the current authenticated user and displays a welcome message. Since we’re passing the logout() method to all of our consumers, we can also give the user the option of logging out from anywhere in the component tree.

const Dashboard = () => ( <Auth.Consumer> {({ user, logout }) => ( <div> <div>Hello {user.name}</div> <button onClick={e => logout(e)}> Logout </button> </div> )} </Auth.Consumer>
);

Building larger applications with statecharts

Using finite state machines with React doesn’t have to be limited to authentication, nor does it have to be limited to the context API.

Using statecharts, you can have hierarchical machines and/or parallel machines — meaning individual React components can have their own internal state machine, but still be connected to the overall state of your application.

In this article, we’ve primarily focused on using xstate directly with the native Context API; in larger applications, I highly recommend looking at react-automata, which provides a thin layer of abstraction over the top of xstate. react-automata has the added benefit of being able to automagically generate Jest tests for your components.

State machines and state management tools are not mutually exclusive

It’s easy to get confused by thinking you must either use, say, xstate or Redux; but it’s important to note that state machines are more of an implementation concept, concerned with how you design your state — not necessarily how you manage it.

In fact, state machines can be used with just about any un-opinionated state management tool. I encourage you to explore various approaches to determine what works best for you, your team, and your application(s).

In conclusion

These concepts can be extended to the real world without having to refactor your entire application. State machines are an excellent refactor target — meaning, next time you’re working on a component that is littered with boolean flags like isFetching and isError, consider refactoring that component to use a state machine.

As a front-end developer, I’ve found that I’m often fixing one of two categories of bugs: display-related issues or unexpected application states.

State machines make the second category virtually disappear.

If you’re interested in diving deeper into state machines, I’ve spent the last several months working on a course on finite state machines — if you sign up for the email list, you’ll receive a discount code when the course launches in August.

The post Finite State Machines with React appeared first on CSS-Tricks.