Tag: React

Building a Real-Time Chat App with React and Firebase

In this article, we’ll cover key concepts for authenticating a user with Firebase in a real-time chat application. We’ll integrate third-party auth providers (e.g. Google, Twitter and GitHub) and, once users are signed in, we’ll learn how to store user chat data in the Firebase Realtime Database, where we can sync data with a NoSQL cloud database.

The client application is going to be built in React, as it is one of the most popular JavaScript frameworks out there, but the concepts can also be applied to other frameworks.

But first, what is Firebase?

Firebase is Google’s mobile platform for quickly developing apps. Firebase provides a suite of tools for authenticating applications, building reactive client apps, reporting analytics, as well as a host of other helpful resources for managing apps in general. It also provides back-end management for web, iOS, Android, and Unity, a 3D development platform.

Out of the box, Firebase is packaged with features that help developers like ourselves focus on building apps while it handles all server-side logic. Things like:

  • Authentication: This includes support for email and password authentication as well as single sign-on capabilities (via Facebook, Twitter and Google).
  • Realtime database: This is a “NoSQL” database that updates in real time.
  • Cloud functions: These run extra server-side logic.
  • Static hosting: This is a means of serving assets pre-built instead of rendering at runtime.
  • Cloud storage: This gives us a place to store media assets.

Firebase offers a generous free tier that includes authentication and access to their Realtime Database. The authentication providers we’ll be covering email and password — Google and GitHub — are free on that side as well. The Realtime Database allows up to 100 simultaneous connections and 1 gigabyte storage per month. A full table of pricing can be found on the Firebase website.

Here’s what we’re making

We’re going to build an application called Chatty. It will allow only authenticated users to send and read messages and users can sign up by providing their email and creating a password, or by authenticating through a Google or GitHub account. Check out source code if you want to refer to it or take a peek as we get started.

We’ll end up with something like this:

Setting up

You’re going to need a Google account to use Firebase, so snag one if you haven’t already. And once you do, we can officially kick the tires on this thing.

First off, head over to the Firebase Console and click the “Add project” option.

Next, let’s enter a name for the project. I’m going with Chatty.

You can choose to add analytics to your project, but it’s not required. Either way, click continue to proceed and Firebase will take a few seconds to delegate resources for the project.

Once that spins up, we are taken to the Firebase dashboard But, before we can start using Firebase in our web app, we have to get the configuration details down for our project. So, click on the web icon in the dashboard.

Then, enter a name for the app and click Register app.

Next up, we’ll copy and store the configuration details on the next screen in a safe place. That will come in handy in the next step.

Again, we’re going to authenticate users via email and password, with additional options for single sign-on with a Google or GitHub account. We need to enable these from the Authentication tab in the dashboard, but we’ll go through each of them one at a time.

Email and password authentication

There’s a Sign-in method tab in the Firebase dashboard. Click the Email/Password option and enable it.

Now we can use it in our app!

Setting up the web app

For our web app, we’ll be using React but most of the concepts can be applied to any other framework. Well need Node.js for a React setup, so download and install it if you haven’t already.

We’ll use create-react-app to bootstrap a new React project. This downloads and installs the necessary packages required for a React application. In the terminal, cd into where you’d like our Chatty project to go and run this to initialize it:

npx create-react-app chatty

This command does the initial setup for our react app and installs the dependencies in package.json. We’ll also install some additional packages. So, let’s cd into the project itself and add packages for React Router and Firebase.

cd chatty yarn add react-router-dom firebase

We already know why we need Firebase, but why React Router? Our chat app will have a couple of views we can use React Router to handle navigating between pages.

With that done, we can officially start the app:

yarn start

This starts a development server and opens a URL in your default browser. If everything got installed correctly, you should see a screen like this:

Looking at the folder structure, you would see something similar to this:

For our chat app, this is the folder structure we’ll be using:

  • /components: contains reusable widgets used in different pages
  • /helpers: a set of reusable functions
  • /pages: the app views
  • /services: third-party services that we’re using (e.g. Firebase)
  • App.js: the root component

Anything else in the folder is unnecessary for this project and can safely be removed. From here, let’s add some code to src/services/firebase.js so the app can talk with Firebase.

import firebase from 'firebase';

Let’s get Firebase into the app

We’ll import and initialize Firebase using the configuration details we copied earlier when registering the app in the Firebase dashboard. Then, we’ll export the authentication and database modules.

const config = {   apiKey: "ADD-YOUR-DETAILS-HERE",   authDomain: "ADD-YOUR-DETAILS-HERE",   databaseURL: "ADD-YOUR-DETAILS-HERE" }; firebase.initializeApp(config); export const auth = firebase.auth; export const db = firebase.database();

Let’s import our dependencies in src/App.js:

import React, { Component } from 'react'; import {   Route,   BrowserRouter as Router,   Switch,   Redirect, } from "react-router-dom"; import Home from './pages/Home'; import Chat from './pages/Chat'; import Signup from './pages/Signup'; import Login from './pages/Login'; import { auth } from './services/firebase';

These are ES6 imports. Specifically, we’re importing React and other packages needed to build out the app. We’re also importing all the pages of our app that we’ll configure later to our router.

Next up is routing

Our app has public routes (accessible without authentication) and a private route (accessible only with authentication). Because React doesn’t provide a way to check the authenticated state, we’ll create higher-order components (HOCs) for both types of routes.

Our HOCs will:

  • wrap a <Route>,
  • pass props from the router to the <Route>,
  • render the component depending on the authenticated state, and
  • redirect the user to a specified route if the condition is not met

Let’s write the code for our <PrivateRoute> HOC.

function PrivateRoute({ component: Component, authenticated, ...rest }) {   return (     <Route       {...rest}       render={(props) => authenticated === true         ? <Component {...props} />         : <Redirect to={{ pathname: '/login', state: { from: props.location } }} />}     />   ) }

It receives three props: the component to render if the condition is true, the authenticated state, and the ES6 spread operator to get the remaining parameters passed from the router. It checks if authenticated is true and renders the component passed, else it redirects to/login.

function PublicRoute({ component: Component, authenticated, ...rest }) {   return (     <Route       {...rest}       render={(props) => authenticated === false         ? <Component {...props} />         : <Redirect to='/chat' />}     />   ) }

The <PublicRoute> is pretty much the same. It renders our public routes and redirects to the /chat path if the authenticated state becomes true. We can use the HOCs in our render method:

render() {   return this.state.loading === true ? <h2>Loading...</h2> : (     <Router>       <Switch>         <Route exact path="/" component={Home}></Route>         <PrivateRoute path="/chat" authenticated={this.state.authenticated} component={Chat}></PrivateRoute>         <PublicRoute path="/signup" authenticated={this.state.authenticated} component={Signup}></PublicRoute>         <PublicRoute path="/login" authenticated={this.state.authenticated} component={Login}></PublicRoute>       </Switch>     </Router>   ); }

Checking for authentication

It would be nice to show a loading indicator while we verify if the user is authenticated. Once the check is complete, we render the appropriate route that matches the URL. We have three public routes — <Home>, <Login> and <Signup> — and a private one called <Chat>.

Let’s write the logic to check if the user is indeed authenticated.

class App extends Component {   constructor() {     super();     this.state = {       authenticated: false,       loading: true,     };   } }  export default App;

Here we’re setting the initial state of the app. Then, we’re using the componentDidMount lifecycle hook to check if the user is authenticated. So, let’s add this after the constructor:

componentDidMount() {   this.removelistener = auth().onAuthStateChanged((user) => {     if (user) {       this.setState({         authenticated: true,         loading: false,       });     } else {       this.setState({         authenticated: false,         loading: false,       });     }   }) }

Firebase provides an intuitive method called onAuthStateChanged that is triggered when the authenticated state changes. We use this to update our initial state. user is null if the user is not authenticated. If the user is true, we update authenticated to true; else we set it to false. We also set loading to false either way.

Registering users with email and password

Users will be able to register for Chatty through email and password. The helpers folder contains a set of methods that we’ll use to handle some authentication logic. Inside this folder, let’s create a new file called auth.js and add this:

import { auth } from "../services/firebase";

We import the auth module from the service we created earlier.

export function signup(email, password) {   return auth().createUserWithEmailAndPassword(email, password); } 
 export function signin(email, password) {   return auth().signInWithEmailAndPassword(email, password); }

We have two methods here: signup andsignin:

  • signup will create a new user using their email and password. 
  • signin will log in an existing user created with email and password.

Let’s create our <Signup> page by creating a new file Signup.js file in the pages folder. This is the markup for the UI:

import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import { signup } from '../helpers/auth'; 
 export default class SignUp extends Component { 
   render() {     return (       <div>         <form onSubmit={this.handleSubmit}>           <h1>             Sign Up to           <Link to="/">Chatty</Link>           </h1>           <p>Fill in the form below to create an account.</p>           <div>             <input placeholder="Email" name="email" type="email" onChange={this.handleChange} value={this.state.email}></input>           </div>           <div>             <input placeholder="Password" name="password" onChange={this.handleChange} value={this.state.password} type="password"></input>           </div>           <div>             {this.state.error ? <p>{this.state.error}</p> : null}             <button type="submit">Sign up</button>           </div>           <hr></hr>           <p>Already have an account? <Link to="/login">Login</Link></p>         </form>       </div>     )   } }
Email? Check. Password? Check. Submit button? Check. Our form is looking good.

The form and input fields are bound to a method we haven’t created yet, so let’s sort that out. Just before the render() method, we’ll add the following:

constructor(props) {   super(props);   this.state = {     error: null,     email: '',     password: '',   };   this.handleChange = this.handleChange.bind(this);   this.handleSubmit = this.handleSubmit.bind(this); }

We’re setting the initial state of the page. We’re also binding the handleChange and handleSubmit methods to the component’s this scope.

handleChange(event) {   this.setState({     [event.target.name]: event.target.value   }); }

Next up, we’ll add the handleChange method that our input fields are bound to. The method uses computed properties to dynamically determine the key and set the corresponding state variable.

async handleSubmit(event) {   event.preventDefault();   this.setState({ error: '' });   try {     await signup(this.state.email, this.state.password);   } catch (error) {     this.setState({ error: error.message });   } }

For handleSubmit, we’re preventing the default behavior for form submissions (which simply reloads the browser, among other things). We’re also clearing up the error state variable, then using the signup() method imported from helpers/auth to pass the email and password entered by the user.

If the registration is successful, users get redirected to the /Chats route. This is possible with the combination of onAuthStateChanged and the HOCs we created earlier. If registration fails, we set the error variable which displays a message to users.

Authenticating users with email and password

The login page is identical to the signup page. The only difference is we’ll be using the signin method from the helpers we created earlier. That said, let’s create yet another new file in the pages directory, this time called Login.js, with this code in it:

import React, { Component } from "react"; import { Link } from "react-router-dom"; import { signin, signInWithGoogle, signInWithGitHub } from "../helpers/auth"; 
 export default class Login extends Component {   constructor(props) {     super(props);     this.state = {       error: null,       email: "",       password: ""     };     this.handleChange = this.handleChange.bind(this);     this.handleSubmit = this.handleSubmit.bind(this);   } 
   handleChange(event) {     this.setState({       [event.target.name]: event.target.value     });   } 
   async handleSubmit(event) {     event.preventDefault();     this.setState({ error: "" });     try {       await signin(this.state.email, this.state.password);     } catch (error) {       this.setState({ error: error.message });     }   } 
   render() {     return (       <div>         <form           autoComplete="off"           onSubmit={this.handleSubmit}         >           <h1>             Login to             <Link to="/">               Chatty             </Link>           </h1>           <p>             Fill in the form below to login to your account.           </p>           <div>             <input               placeholder="Email"               name="email"               type="email"               onChange={this.handleChange}               value={this.state.email}             />           </div>           <div>             <input               placeholder="Password"               name="password"               onChange={this.handleChange}               value={this.state.password}               type="password"             />           </div>           <div>             {this.state.error ? (               <p>{this.state.error}</p>             ) : null}             <button type="submit">Login</button>           </div>           <hr />           <p>             Don't have an account? <Link to="/signup">Sign up</Link>           </p>         </form>       </div>     );   } }

Again, very similar to before. When the user successfully logs in, they’re redirected to /chat.

Authenticating with a Google account

Firebase allows us to authenticate users with a valid Google account. We’ve got to enable it in the Firebase dashboard just like we did for email and password.

Select the Google option and enable it in the settings.

On that same page, we also need to scroll down to add a domain to the list of domains that are authorized to access feature. This way, we avoid spam from any domain that is not whitelisted. For development purposes, our domain is localhost, so we’ll go with that for now.

We can switch back to our editor now. We’ll add a new method to helpers/auth.js to handle Google authentication.

export function signInWithGoogle() {   const provider = new auth.GoogleAuthProvider();   return auth().signInWithPopup(provider); }

Here, we’re creating an instance of the GoogleAuthProvider. Then, we’re calling signInWithPopup with the provider as a parameter. When this method is called, a pop up will appear and take the user through the Google sign in flow before redirecting them back to the app. You’ve likely experienced it yourself at some point in time.

Let’s use it in our signup page by importing the method:

import { signin, signInWithGoogle } from "../helpers/auth";

Then, let’s add a button to trigger the method, just under the Sign up button:

<p>Or</p> <button onClick={this.googleSignIn} type="button">   Sign up with Google </button>

Next, we’ll add the onClick handler:

async googleSignIn() {   try {     await signInWithGoogle();   } catch (error) {     this.setState({ error: error.message });   } }

Oh, and we should remember to bind the handler to the component:

constructor() {   // ...   this.githubSignIn = this.githubSignIn.bind(this); }

That’s all we need! When the button is clicked, it takes users through the Google sign in flow and, if successful, the app redirects the user to the chat route.

Authenticating with a GitHub account

We’re going to do the same thing with GitHub. May as well give folks more than one choice of account.

Let’s walk through the steps. First, we’ll enable GitHub sign in on Firebase dashboard, like we did for email and Google.

You will notice both the client ID and client secret fields are empty, but we do have our authorization callback URL at the bottom. Copy that, because we’ll use it when we do our next thing, which is register the app on GitHub.

Once that’s done, we’ll get a client ID and secret which we can now add to the Firebase console.

Let’s switch back to the editor and add a new method to helpers/auth.js:

export function signInWithGitHub() {   const provider = new auth.GithubAuthProvider();   return auth().signInWithPopup(provider); }

It’s similar to the Google sign in interface, but this time we’re creating a GithubAuthProvider. Then, we’ll call signInWithPopup with the provider.

In pages/Signup.js, we update our imports to include the signInWithGitHub method:

import { signup, signInWithGoogle, signInWithGitHub } from "../helpers/auth";

We add a button for GitHub sign up:

<button type="button" onClick={this.githubSignIn}>   Sign up with GitHub </button>

Then we add a click handler for the button which triggers the GitHub sign up flow:

async githubSignIn() {   try {     await signInWithGitHub();   } catch (error) {     this.setState({ error: error.message });   } }

Let’s remember again to bind the handler to the component:

constructor() {   // ...   this.githubSignIn = this.githubSignIn.bind(this); }

Now we’ll get the same sign-in and authentication flow that we have with Google, but with GitHub.

Reading data from Firebase

Firebase has two types of databases: A product they call Realtime Database and another called Cloud Firestore. Both databases are NoSQL-like databases, meaning the database is structured as key-value pairs. For this tutorial, we’ll use the Realtime Database.

This is the structure we’ll be using for our app. We have a root node chats with children nodes. Each child has a content, timestamp, and user ID. One of the tabs you’ll notice is Rules which is how we set permissions on the contents of the database.

Firebase database rules are defined as key-value pairs as well. Here, we’ll set our rules to allow only authenticated users to read and write to the chat node. There are a lot more firebase rules. worth checking out.

Let’s write code to read from the database. First, create a new file called Chat.js  in the pages  folder and add this code to import React, Firebase authentication, and Realtime Database:

import React, { Component } from "react"; import { auth } from "../services/firebase"; import { db } from "../services/firebase"

Next, let’s define the initial state of the app:

export default class Chat extends Component {   constructor(props) {     super(props);     this.state = {       user: auth().currentUser,       chats: [],       content: '',       readError: null,       writeError: null     };   }   async componentDidMount() {     this.setState({ readError: null });     try {       db.ref("chats").on("value", snapshot => {         let chats = [];         snapshot.forEach((snap) => {           chats.push(snap.val());         });         this.setState({ chats });       });     } catch (error) {       this.setState({ readError: error.message });     }   } }

The real main logic takes place in componentDidMount. db.ref("chats") is a reference to the chats path in the database. We listen to the value event which is triggered anytime a new value is added to the chats node. What is returned from the database is an array-like object that we loop through and push each object into an array. Then, we set the chats state variable to our resulting array. If there is an error, we set the readError state variable to the error message.

One thing to note here is that a connection is created between the client and our Firebase database because we used the .on() method. This means any time a new value is added to the database, the client app is updated in real-time which means users can see new chats without a page refresh Nice!.

After componentDidMount, we can render our chats like so:

render() {   return (     <div>       <div className="chats">         {this.state.chats.map(chat => {           return <p key={chat.timestamp}>{chat.content}</p>         })}       </div>       <div>         Login in as: <strong>{this.state.user.email}</strong>       </div>     </div>   ); }

This renders the array of chats. We render the email of the currently logged in user.

Writing data to Firebase

At the moment, users can only read from the database but are unable to send messages. What we need is a form with an input field that accepts a message and a button to send the message to the chat.

So, let’s modify the markup like so:

return (     <div>       <div className="chats">         {this.state.chats.map(chat => {           return <p key={chat.timestamp}>{chat.content}</p>         })}       </div>       {# message form #}       <form onSubmit={this.handleSubmit}>         <input onChange={this.handleChange} value={this.state.content}></input>         {this.state.error ? <p>{this.state.writeError}</p> : null}         <button type="submit">Send</button>       </form>       <div>         Login in as: <strong>{this.state.user.email}</strong>       </div>     </div>   ); }

We have added a form with an input field and a button. The value of the input field is bound to our state variable content and we call handleChange when its value changes.

handleChange(event) {   this.setState({     content: event.target.value   }); }

handleChange gets the value from the input field and sets on our state variable. To submit the form, we call handleSubmit:

async handleSubmit(event) {   event.preventDefault();   this.setState({ writeError: null });   try {     await db.ref("chats").push({       content: this.state.content,       timestamp: Date.now(),       uid: this.state.user.uid     });     this.setState({ content: '' });   } catch (error) {     this.setState({ writeError: error.message });   } }

We set any previous errors to null. We create a reference to the chats node in the database and use push() to create a unique key and pushe the object to it.

As always, we have to bind our methods to the component:

constructor(props) {   // ...   this.handleChange = this.handleChange.bind(this);   this.handleSubmit = this.handleSubmit.bind(this); }

Now a user can add new messages to the chats and see them in real-time! How cool is that?

Demo time!

Enjoy your new chat app!

Congratulations! You have just built a chat tool that authenticates users with email and password, long with options to authenticate through a Google or GitHub account.

I hope this give you a good idea of how handy Firebase can be to get up and running with authentication on an app. We worked on a chat app, but the real gem is the signup and sign-in methods we created to get into it. That’s something useful for many apps.

Questions? Thoughts? Feedback? Let me know in the comments!

The post Building a Real-Time Chat App with React and Firebase appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

React Suspense in Practice

This post is about understanding how Suspense works, what it does, and seeing how it can integrate into a real web app. We’ll look at how to integrate routing and data loading with Suspense in React. For routing, I’ll be using vanilla JavaScript, and I’ll be using my own micro-graphql-react GraphQL library for data.

If you’re wondering about React Router, it seems great, but I’ve never had the chance to use it. My own side project has a simple enough routing story that I always just did it by hand. Besides, using vanilla JavaScript will give us a better look at how Suspense works.

A little background

Let’s talk about Suspense itself. Kingsley Silas provides a thorough overview of it, but the first thing to note is that it’s still an experimental API. That means — and React’s docs say the same — not to lean on it yet for production-ready work. There’s always a chance it will change between now and when it’s fully complete, so please bear that in mind.

That said, Suspense is all about maintaining a consistent UI in the face of asynchronous dependencies, such as lazily loaded React components, GraphQL data, etc. Suspense provides low-level API’s that allow you to easily maintain your UI while your app is managing these things.

But what does “consistent” mean in this case? It means not rendering a UI that’s partially complete. It means, if there are three data sources on the page, and one of them has completed, we don’t want to render that updated piece of state, with a spinner next to the now-outdated other two pieces of state.

What we do want to do is indicate to the user that data are loading, while continuing to show either the old UI, or an alternative UI which indicates we’re waiting on data; Suspense supports either, which I’ll get into.

What exactly Suspense does

This is all less complicated than it may seem. Traditionally in React, you’d set state, and your UI would update. Life was simple. But it also led to the sorts of inconsistencies described above. What Suspense adds is the ability to have a component notify React at render time that it’s waiting for asynchronous data; this is called suspending, and it can happen anywhere in a component’s tree, as many times as needed, until the tree is ready. When a component suspends, React will decline to render the pending state update until all suspended dependencies have been satisfied.

So what happens when a component suspends? React will look up the tree, find the first <Suspense> component, and render its fallback. I’ll be providing plenty of examples, but for now, know that you can provide this:

<Suspense fallback={<Loading />}>

…and the <Loading /> component will render if any child components of <Suspense> are suspended.

But what if we already have a valid, consistent UI, and the user loads new data, causing a component to suspend? This would cause the entire existing UI to un-render, and the fallback to show. That’d still be consistent, but hardly a good UX. We’d prefer the old UI stay on the screen while the new data are loading.

To support this, React provides a second API, useTransition, which effectively makes a state change in memory. In other words, it allows you to set state in memory while keeping your existing UI on screen; React will literally keep a second copy of your component tree rendered in memory, and set state on that tree. Components may suspend, but only in memory, so your existing UI will continue to show on the screen. When the state change is complete, and all suspensions have resolved, the in-memory state change will render onto the screen. Obviously you want to provide feedback to your user while this is happening, so useTransition provides a pending boolean, which you can use to display some sort of inline “loading” notification while suspensions are being resolved in memory.

When you think about it, you probably don’t want your existing UI to show indefinitely while your loading is pending. If the user tries to do something, and a long period of time elapses before it’s finished, you should probably consider the existing UI outdated and invalid. At this point, you probably will want your component tree to suspend, and your <Suspense> fallback to display.

To accomplish this, useTransition takes a timeoutMs value. This indicates the amount of time you’re willing to let the in-memory state change run, before you suspend.

const Component = props => {   const [startTransition, isPending] = useTransition({ timeoutMs: 3000 });   // ..... };

Here, startTransition is a function. When you want to run a state change “in memory,” you call startTransition, and pass a lambda expression that does your state change.

startTransition(() => {   dispatch({ type: LOAD_DATA_OR_SOMETHING, value: 42 }); })

You can call startTransition wherever you want. You can pass it to child components, etc. When you call it, any state change you perform will happen in memory. If a suspension happens, isPending will become true, which you can use to display some sort of inline loading indicator.

That’s it. That’s what Suspense does.

The rest of this post will get into some actual code to leverage these features.

Example: Navigation

To tie navigation into Suspense, you’ll be happy to know that React provides a primitive to do this: React.lazy. It’s a function that takes a lambda expression that returns a Promise, which resolves to a React component. The result of this function call becomes your lazily loaded component. It sounds complicated, but it looks like this:

const SettingsComponent = lazy(() => import("./modules/settings/settings"));

SettingsComponent is now a React component that, when rendered (but not before), will call the function we passed in, which will call import() and load the JavaScript module located at ./modules/settings/settings.

The key piece is this: while that import() is in flight, the component rendering SettingsComponent will suspend. It seems we have all the pieces in hand, so let’s put them together and build some Suspense-based navigation.

Navigation helpers

But first, for context, I’ll briefly cover how navigation state is managed in this app, so the Suspense code will make more sense.

I’ll be using my booklist app. It’s just a side project of mine I mainly keep around to mess around with bleeding-edge web technology. It was written by me alone, so expect parts of it to be a bit unrefined (especially the design).

The app is small, with about eight different modules a user can browse to, without any deeper navigation. Any search state a module might use is stored in the URL’s query string. With this in mind, there are a few methods which scrape the current module name, and search state from the URL. This code uses the query-string and history packages from npm, and looks somewhat like this (some details have been removed for simplicity, like authentication).

import createHistory from "history/createBrowserHistory"; import queryString from "query-string"; export const history = createHistory(); export function getCurrentUrlState() {   let location = history.location;   let parsed = queryString.parse(location.search);   return {     pathname: location.pathname,     searchState: parsed   }; } export function getCurrentModuleFromUrl() {   let location = history.location;   return location.pathname.replace(///g, "").toLowerCase(); }

I have an appSettings reducer that holds the current module and searchState values for the app, and uses these methods to sync with the URL when needed.

The pieces of a Suspense-based navigation

Let’s get started with some Suspense work. First, let’s create the lazy-loaded components for our modules.

const ActivateComponent = lazy(() => import("./modules/activate/activate")); const AuthenticateComponent = lazy(() =>   import("./modules/authenticate/authenticate") ); const BooksComponent = lazy(() => import("./modules/books/books")); const HomeComponent = lazy(() => import("./modules/home/home")); const ScanComponent = lazy(() => import("./modules/scan/scan")); const SubjectsComponent = lazy(() => import("./modules/subjects/subjects")); const SettingsComponent = lazy(() => import("./modules/settings/settings")); const AdminComponent = lazy(() => import("./modules/admin/admin"));

Now we need a method that chooses the right component based on the current module. If we were using React Router, we’d have some nice <Route /> components. Since we’re rolling this manually, a switch will do.

export const getModuleComponent = moduleToLoad => {   if (moduleToLoad == null) {     return null;   }   switch (moduleToLoad.toLowerCase()) {     case "activate":       return ActivateComponent;     case "authenticate":       return AuthenticateComponent;     case "books":       return BooksComponent;     case "home":       return HomeComponent;     case "scan":       return ScanComponent;     case "subjects":       return SubjectsComponent;     case "settings":       return SettingsComponent;     case "admin":       return AdminComponent;   }      return HomeComponent; };

The whole thing put together

With all the boring setup out of the way, let’s see what the entire app root looks like. There’s a lot of code here, but I promise, relatively few of these lines pertain to Suspense, and I’ll cover all of it.

const App = () => {   const [startTransitionNewModule, isNewModulePending] = useTransition({     timeoutMs: 3000   });   const [startTransitionModuleUpdate, moduleUpdatePending] = useTransition({     timeoutMs: 3000   });   let appStatePacket = useAppState();   let [appState, _, dispatch] = appStatePacket;   let Component = getModuleComponent(appState.module);   useEffect(() => {     startTransitionNewModule(() => {       dispatch({ type: URL_SYNC });     });   }, []);   useEffect(() => {     return history.listen(location => {       if (appState.module != getCurrentModuleFromUrl()) {         startTransitionNewModule(() => {           dispatch({ type: URL_SYNC });         });       } else {         startTransitionModuleUpdate(() => {           dispatch({ type: URL_SYNC });         });       }     });   }, [appState.module]);   return (     <AppContext.Provider value={appStatePacket}>       <ModuleUpdateContext.Provider value={moduleUpdatePending}>         <div>           <MainNavigationBar />           {isNewModulePending ? <Loading /> : null}           <Suspense fallback={<LongLoading />}>             <div id="main-content" style={{ flex: 1, overflowY: "auto" }}>               {Component ? <Component updating={moduleUpdatePending} /> : null}             </div>           </Suspense>         </div>       </ModuleUpdateContext.Provider>     </AppContext.Provider>   ); };

First, we have two different calls to useTransition. We’ll use one for routing to a new module, and the other for updating search state for the current module. Why the difference? Well, when a module’s search state is updating, that module will likely want to display an inline loading indicator. That updating state is held by the moduleUpdatePending variable, which you’ll see I put on context for the active module to grab, and use as needed:

<div>   <MainNavigationBar />   {isNewModulePending ? <Loading /> : null}   <Suspense fallback={<LongLoading />}>     <div id="main-content" style={{ flex: 1, overflowY: "auto" }}>       {Component ? <Component updating={moduleUpdatePending} /> : null} // highlight     </div>   </Suspense> </div>

The appStatePacket is the result of the app state reducer I discussed above (but did not show). It contains various pieces of application state which rarely change (color theme, offline status, current module, etc).

let appStatePacket = useAppState();

A little later, I grab whichever component happens to be active, based on the current module name. Initially this will be null.

let Component = getModuleComponent(appState.module);

The first call to useEffect will tell our appSettings reducer to sync with the URL at startup.

useEffect(() => {   startTransitionNewModule(() => {     dispatch({ type: URL_SYNC });   }); }, []);

Since this is the initial module the web app navigates to, I wrap it in startTransitionNewModule to indicate that a fresh module is loading. While it might be tempting to have the appSettings reducer have the initial module name as its initial state, doing this prevents us from calling our startTransitionNewModule callback, which means our Suspense boundary would render the fallback immediately, instead of after the timeout.

The next call to useEffect sets up a history subscription. No matter what, when the url changes we tell our app settings to sync against the URL. The only difference is which startTransition that same call is wrapped in.

useEffect(() => {   return history.listen(location => {     if (appState.module != getCurrentModuleFromUrl()) {       startTransitionNewModule(() => {         dispatch({ type: URL_SYNC });       });     } else {       startTransitionModuleUpdate(() => {         dispatch({ type: URL_SYNC });       });     }   }); }, [appState.module]);

If we’re browsing to a new module, we call startTransitionNewModule. If we’re loading a component that hasn’t been loaded already, React.lazy will suspend, and the pending indicator visible only to the app’s root will set, which will show a loading spinner at the top of the app while the lazy component is fetched and loaded. Because of how useTransition works, the current screen will continue to show for three seconds. If that time expires and the component is still not ready, our UI will suspend, and the fallback will render, which will show the <LongLoading /> component:

{isNewModulePending ? <Loading /> : null} <Suspense fallback={<LongLoading />}>   <div id="main-content" style={{ flex: 1, overflowY: "auto" }}>     {Component ? <Component updating={moduleUpdatePending} /> : null}   </div> </Suspense>

If we’re not changing modules, we call startTransitionModuleUpdate:

startTransitionModuleUpdate(() => {   dispatch({ type: URL_SYNC }); });

If the update causes a suspension, the pending indicator we’re putting on context will be triggered. The active component can detect that and show whatever inline loading indicator it wants. As before, if the suspension takes longer than three seconds, the same Suspense boundary from before will be triggered… unless, as we’ll see later, there’s a Suspense boundary lower in the tree.

One important thing to note is that these three-second timeouts apply not only to the component loading, but also being ready to display. If the component loads in two seconds, and, when rendering in memory (since we’re inside of a startTransition call) suspends, the useTransition will continue to wait for up to one more second before Suspending.

In writing this blog post, I used Chrome’s slow network modes to help force loading to be slow, to test my Suspense boundaries. The settings are in the Network tab of Chrome’s dev tools.

Let’s open our app to the settings module. This will be called:

dispatch({ type: URL_SYNC });

Our appSettings reducer will sync with the URL, then set module to “settings.” This will happen inside of startTransitionNewModule so that, when the lazy-loaded component attempts to render, it’ll suspend. Since we’re inside startTransitionNewModule, the isNewModulePending will switch over to true, and the <Loading /> component will render.

If the component is still not ready to render within three seconds, the in-memory version of our component tree will switch over, suspend, and our Suspense boundary will render the <LongLoading /> component.
When it’s done, the settings module will show.

So what happens when we browse somewhere new? Basically the same thing as before, except this call:

dispatch({ type: URL_SYNC });

…will come from the second instance of useEffect. Let’s browse to the books module and see what happens. First, the inline spinner shows as expected:

If the three-second timeout elapses, our Suspense boundary will render its fallback:
And, eventually, our books module loads:

Searching and updating

Let’s stay within the books module, and update the URL search string to kick off a new search. Recall from before that we were detecting the same module in that second useEffect call and using a dedicated useTransition call for it. From there, we were putting the pending indicator on context for whichever module was active for us to grab and use.

Let’s see some code to actually use that. There’s not really much Suspense-related code here. I’m grabbing the value from context, and if true, rendering an inline spinner on top of my existing results. Recall that this happens when a useTransition call has begun, and the app is suspended in memory. While that’s happening, we continue to show the existing UI, but with this loading indicator.

const BookResults: SFC<{ books: any; uiView: any }> = ({ books, uiView }) => {   const isUpdating = useContext(ModuleUpdateContext);   return (     <>       {!books.length ? (         <div           className="alert alert-warning"           style={{ marginTop: "20px", marginRight: "5px" }}         >           No books found         </div>       ) : null}       {isUpdating ? <Loading /> : null}       {uiView.isGridView ? (         <GridView books={books} />       ) : uiView.isBasicList ? (         <BasicListView books={books} />       ) : uiView.isCoversList ? (         <CoversView books={books} />       ) : null}     </>   ); };

Let’s set a search term and see what happens. First, the inline spinner displays.

Then, if the useTransition timeout expires, we’ll get the Suspense boundary’s fallback. The books module defines its own Suspense boundary in order to provide a more fine-tuned loading indicator, which looks like this:

This is a key point. When making Suspense boundary fallbacks, try not to throw up any sort of spinner and “loading” message. That made sense for our top-level navigation because there’s not much else to do. But when you’re in a specific part of your application, try to make your fallback re-use many of the same components with some sort of loading indicator where the data would be — but with everything else disabled.

This is what the relevant components look like for my books module:

const RenderModule: SFC<{}> = ({}) => {   const uiView = useBookSearchUiView();   const [lastBookResults, setLastBookResults] = useState({     totalPages: 0,     resultsCount: 0   });   return (     <div className="standard-module-container margin-bottom-lg">       <Suspense fallback={<Fallback uiView={uiView} {...lastBookResults} />}>         <MainContent uiView={uiView} setLastBookResults={setLastBookResults} />       </Suspense>     </div>   ); }; const Fallback: SFC<{   uiView: BookSearchUiView;   totalPages: number;   resultsCount: number; }> = ({ uiView, totalPages, resultsCount }) => {   return (     <>       <BooksMenuBarDisabled         totalPages={totalPages}         resultsCount={resultsCount}       />       {uiView.isGridView ? (         <GridViewShell />       ) : (         <h1>           Books are loading <i className="fas fa-cog fa-spin"></i>         </h1>       )}     </>   ); };

A quick note on consistency

Before we move on, I’d like to point out one thing from the earlier screenshots. Look at the inline spinner that displays while the search is pending, then look at the screen when that search suspended, and next, the finished results:

Notice how there’s a “C++” label to the right of the search pane, with an option to remove it from the search query? Or rather, notice how that label is only on the second two screenshots? The moment the URL updates, the application state governing that label is updated; however, that state does not initially display. Initially, the state update suspends in memory (since we used useTransition), and the prior UI continues to show.

Then the fallback renders. The fallback renders a disabled version of that same search bar, which does show the current search state (by choice). We’ve now removed our prior UI (since by now it’s quite old, and stale) and are waiting on the search shown in the disabled menu bar.

This is the sort of consistency Suspense gives you, for free.

You can spend your time crafting nice application states, and React does the leg work of surmising whether things are ready, without you needing to juggle promises.

Nested Suspense boundaries

Let’s suppose our top-level navigation takes a while to load our books component to the extent that our “Still loading, sorry” spinner from the Suspense boundary renders. From there, the books component loads and the new Suspense boundary inside the books component renders. But, then, as rendering continues, our book search query fires, and suspends. What will happen? Will the top-level Suspense boundary continue to show, until everything is ready, or will the lower-down Suspense boundary in books take over?

The answer is the latter. As new Suspense boundaries render lower in the tree, their fallback will replace the fallback of whatever antecedent Suspense fallback was already showing. There’s currently an unstable API to override this, but if you’re doing a good job of crafting your fallbacks, this is probably the behavior you want. You don’t want “Still loading, sorry” to just keep showing. Rather, as soon as the books component is ready, you absolutely want to display that shell with the more targeted waiting message.

Now, what if our books module loads and starts to render while the startTransition spinner is still showing and then suspends? In other words, imagine that our startTransition has a timeout of three seconds, the books component renders, the nested Suspense boundary is in the component tree after one second, and the search query suspends. Will the remaining two seconds elapse before that new nested Suspense boundary renders the fallback, or will the fallback show immediately? The answer, perhaps surprisingly, is that the new Suspense fallback will show immediately by default. That’s because it’s best to show a new, valid UI as quickly as possible, so the user can see that things are happening, and progressing. 

How data fits in

Navigation is fine, but how does data loading fit into all of this?

It fits in completely and transparently. Data loading triggers suspensions just like navigation with React.lazy, and it hooks into all the same useTransition and Suspense boundaries. This is what’s so amazing about Suspense: all your async dependencies seamlessly work in this same system. Managing these various async requests manually to ensure consistency was a nightmare before Suspense, which is precisely why nobody did it. Web apps were notorious for cascading spinners that stopped at unpredictable times, producing inconsistent UIs that were only partially finished.

OK, but how do we actually tie data loading into this? Data loading in Suspense is paradoxically both more complex, and also simple.

I’ll explain.

If you’re waiting on data, you’ll throw a promise in the component that reads (or attempts to read) the data. The promise should be consistent based on the data request. So, four repeated requests for that same “C++” search query should throw the same, identical promise. This implies some sort of caching layer to manage all this. You’ll likely not write this yourself. Instead, you’ll just hope, and wait for the data library you use to update itself to support Suspense.

This is already done in my micro-graphql-react library. Instead of using the useQuery hook, you’ll use the useSuspenseQuery hook, which has an identical API, but throws a consistent promise when you’re waiting on data.

Wait, what about preloading?!

Has your brain turned to mush reading other things on Suspense that talked about waterfalls, fetch-on-render, preloading, etc? Don’t worry about it. Here’s what it all means.

Let’s say you lazy load the books component, which renders and then requests some data, which causes a new Suspense. The network request for the component and the network request for the data will happen one after the other—in a waterfall fashion.

But here’s the key part: the application state that led to whatever initial query that ran when the component loaded was already available when you started loading the component (which, in this case, is the URL). So why not “start” the query as soon as you know you’ll need it? As soon as you browse to /books, why not fire off the current search query right then and there, so it’s already in flight when the component loads.

The micro-graphql-react module does indeed have a preload method, and I urge you to use it. Preloading data is a nice performance optimization, but it has nothing to do with Suspense. Classic React apps could (and should) preload data as soon as they know they’ll need it. Vue apps should preload data as soon as they know they’ll need it. Svelte apps should… you get the point.

Preloading data is orthogonal to Suspense, which is something you can do with literally any framework. It’s also something we all should have been doing already, even though nobody else was.

But seriously, how do you preload?

That’s up to you. At the very least, the logic to run the current search absolutely needs to be completely separated into its own, standalone module. You should literally make sure this preload function is in a file by itself. Don’t rely on webpack to treeshake; you’ll likely face abject sadness the next time you audit your bundles.

You have a preload() method in its own bundle, so call it. Call it when you know you’re about to navigate to that module. I assume React Router has some sort of API to run code on a navigation change. For the vanilla routing code above, I call the method in that routing switch from before. I had omitted it for brevity, but the books entry actually looks like this:

switch (moduleToLoad.toLowerCase()) {   case "activate":     return ActivateComponent;   case "authenticate":     return AuthenticateComponent;   case "books":     // preload!!!     booksPreload();     return BooksComponent;

That’s it. Here’s a live demo to play around with:

To modify the Suspense timeout value, which defaults to 3000ms, navigate to Settings, and check out the misc tab. Just be sure to refresh the page after modifying it.

Wrapping up

I’ve seldom been as excited for anything in the web dev ecosystem as I am for Suspense. It’s an incredibly ambitious system for managing one of the trickiest problems in web development: asynchrony.

The post React Suspense in Practice appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

What React Does (and Doesn’t Do)

With a name as big as React, it’s bound to cause some Stream-Crossing Confusion, as I like to call it. How do you center a <div> in React? Dave Ceddia:

React cares exactly zero about styling. Think of it as generating the barebones HTML. React will put elements on the page, but everything after that is the job of CSS: how they appear, what they look like, how they’re positioned, and how centered or uncentered they are.

“How to center a div in React” is… not a React problem. It’s a CSS problem. You don’t need “react” in your Google query. Once you figure it out, use React to apply the right CSS class name to your components

How do you center a <div> in WordPress? How do you center a <div> in Vue? On one hand, they are broken questions. Those technologies don’t have anything to do with centering things. Centering on the web is something CSS does. On the other hand, higher-level tech is sometimes involved. Maybe there is some Gutenberg thing in WordPress that handles centering on this particular site that you should use for editorial consistency. Maybe there is some styling library being used which means you can’t write regular CSS — you have to write the way the library wants you to write it. Complicated, all this “making websites” stuff.

Direct Link to ArticlePermalink

The post What React Does (and Doesn’t Do) appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

The Hooks of React Router

React Router 5 embraces the power of hooks and has introduced four different hooks to help with routing. You will find this article useful if you are looking for a quick primer on the new patterns of React Router. But before we look at hooks, we will start off with a new route rendering pattern.

Before React Router 5

// When you wanted to render the route and get router props for component <Route path="/" component={Home} /> 
 // Or when you wanted to pass extra props <Route path="/" render={({ match }) => <Profile match={match} mine={true} />}>

When using the component syntax, route props (match, location and history) are implicitly being passed on to the component. But it has to be changed to render once you have extra props to pass to the component. Note that adding an inline function to the component syntax would lead to the component re-mounting on every render.

After React Router 5

<Route path="/">   <Home /> </Route>

Note that there is no implicit passing of any props to the Home component. But without changing anything with the Route itself, you can add any extra props to the Home component. You can no longer make the mistake of re-mounting the component on every render and that’s the best kind of API.

But now route props are not being passed implicitly, so how do we access match, history or location? Do we have to wrap all components with withRouter? That is where the hooks steps in.

Note that hooks were introduced in 16.8 version of React, so you need to be above that version to use them.

useHistory

  • Provides access to the history prop in React Router
  • Refers to the history package dependency that the router uses
  • A primary use case would be for programmatic routing with functions, like push, replace, etc.
import { useHistory } from 'react-router-dom';  function Home() {   const history = useHistory();   return <button onClick={() => history.push('/profile')}>Profile</button>; }

useLocation

  • Provides access to the location prop in React Router
  • It is similar to window.location in the browser itself, but this is accessible everywhere as it represents the Router state and location.
  • A primary use case for this would be to access the query params or the complete route string.
import { useLocation } from 'react-router-dom';  function Profile() {   const location = useLocation();   useEffect(() => {     const currentPath = location.pathname;     const searchParams = new URLSearchParams(location.search);   }, [location]);   return <p>Profile</p>; }

Since the location property is immutable, useEffect will call the function every time the route changes, making it perfect to operate on search parameters or current path.

useParams

  • Provides access to search parameters in the URL
  • This was possible earlier only using match.params.
import { useParams, Route } from 'react-router-dom';  function Profile() {   const { name } = useParams();   return <p>{name}'s Profile</p>; }  function Dashboard() {   return (     <>       <nav>         <Link to={`/profile/ann`}>Ann's Profile</Link>       </nav>       <main>         <Route path="/profile/:name">           <Profile />         </Route>       </main>     </>   ); }

useRouteMatch

  • Provides access to the match object
  • If it is provided with no arguments, it returns the closest match in the component or its parents.
  • A primary use case would be to construct nested paths.
import { useRouteMatch, Route } from 'react-router-dom';  function Auth() {   const match = useRouteMatch();   return (     <>       <Route path={`$  {match.url}/login`}>         <Login />       </Route>       <Route path={`$  {match.url}/register`}>         <Register />       </Route>     </>   ); }

You can also use useRouteMatch to access a match without rendering a Route. This is done by passing it the location argument.

For example, consider that you need your own profile to be rendered at /profile and somebody else’s profile if the URL contains the name of the person /profile/dan or /profile/ann. Without using the hook, you would either write a Switch, list both routes and customize them with props. But now, using the hook we can do this:

import {   Route,   BrowserRouter as Router,   Link,   useRouteMatch, } from 'react-router-dom';  function Profile() {   const match = useRouteMatch('/profile/:name');    return match ? <p>{match.params.name}'s Profile</p> : <p>My own profile</p>; }  export default function App() {   return (     <Router>       <nav>         <Link to="/profile">My Profile</Link>         <br />         <Link to={`/profile/ann`}>Ann's Profile</Link>       </nav>       <Route path="/profile">         <Profile />       </Route>     </Router>   ); }

You can also use all the props on Route like exact or sensitive as an object with useRouteMatch.

Wrapping up

The hooks and explicit Route comes with a hidden advantage in itself. After teaching these techniques at multiple workshops, I have come to the realization that these help avoid a lot of confusion and intricacies that came with the earlier patterns. There are suddenly far fewer unforced errors. They will surely help make your router code more maintainable and you will find it way easier to upgrade to new React Router versions.

The post The Hooks of React Router appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Debunking the Myth: Accessibility and React

I find it notable when the blog of a major accessibility-focused company like Deque publishes an article called Debunking the Myth: Accessibility and React. Mark Steadman is essentially saying if a site has bad accessibility, it ain’t React… it’s you. The tools are there to achieve good accessibility.

React didn’t use a <div> for a <button>, but you did. React didn’t force extra markup all over the page when you decided to not use a Fragment. React didn’t forget to change the title of the page, because that was something you neglected.

Is it different how you have to do it in React versus how you have to do it in some other framework or CMS? Yes, it is. Different, but neither worse nor harder.

I’m optimistic that well-made React components focused on accessibility can have a positive impact on the web. Just today I was pair programming and looking at some HTML for a toggle UI in a Rails template. It had a little bug we wanted to fix, which required an HTML change. But this toggle wasn’t a component, it was a chunk of HTML used in dozens of places on the site. Gosh, did I wish this part of the site was architected with proper components instead, a practice that all JavaScript frameworks endorse?

Where did the bad wrap on React come from? Well, we could debate that for days. Is it that JavaScript-focused developers never got the HTML training they needed? Maybe. Was it gnarly, unsemantic React code that was written early on that others copy and pasted too many times? Maybe. I’m not sure we’ll ever know. The important thing is that we all do a better job now.

Direct Link to ArticlePermalink

The post Debunking the Myth: Accessibility and React appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Two Lessons I Learned From Making React Components

Here’s a couple of lessons I’ve learned about how not to build React components. These are things I’ve come across over the past couple of months and thought they might be of interest to you if you’re working on a design system, especially one with a bunch of legacy technical decisions and a lot of tech debt under the hood.

Lesson 1: Avoid child components as much as you can

One thing about working on a big design system with lots of components is that the following pattern eventually starts to become problematic real quick:

<Card>   <Card.Header>Title</Card.Header>   <Card.Body><p>This is some content</p></Card.Body> </Card>

The problematic parts are those child components, Card.Body and Card.Header. This example isn’t terrible because things are relatively simple — it’s when components get more complex that things can get bonkers. For example, each child component can have a whole series of complex props that interfere with the others.

One of my biggest pain points is with our Form components. Take this:

<Form>   <Input />   <Form.Actions>     <Button>Submit</Button>     <Button>Cancel</Button>   </Form.Actions> </Form>

I’m simplifying things considerably, of course, but every time an engineer wants to place two buttons next to each other, they’d import Form.Actions, even if there wasn’t a Form on the page. This meant that everything inside the Form component gets imported and that’s ultimately bad for performance. It just so happens to be bad system design implementation as well.

This also makes things extra difficult when documenting components because now you’ll have to ensure that each of these child components are documented too.

So instead of making Form.Actions a child component, we should’ve made it a brand new component, simply: FormActions (or perhaps something with a better name like ButtonGroup). That way, we don’t have to import Form all the time and we can keep layout-based components separate from the others.

I’ve learned my lesson. From here on out I’ll be avoiding child components altogether where I can.

Lesson 2: Make sure your props don’t conflict with one another

Mandy Michael wrote a great piece about how props can bump into one another and cause all sorts of confusing conflicts, like this TypeScript example:

interface Props {   hideMedia?: boolean   mediaIsEdgeToEdge?: boolean   mediaFullHeight?: boolean   videoInline?: boolean }

Mandy writes:

The purpose of these props are to change the way the image or video is rendered within the card or if the media is rendered at all. The problem with defining them separately is that you end up with a number of flags which toggle component features, many of which are mutually exclusive. For example, you can’t have an image that fills the margins if it’s also hidden.

This was definitely a problem for a lot of the components we inherited in my team’s design systems. There were a bunch of components where boolean props would make a component behave in all sorts of odd and unexpected ways. We even had all sorts of bugs pop up in our Card component during development because the engineers wouldn’t know which props to turn on and turn off for any given effect!

Mandy offers the following solution:

type MediaMode = 'hidden'| 'edgeToEdge' | 'fullHeight'  interface Props {   mediaMode: 'hidden'| 'edgeToEdge' | 'fullHeight' }

In short: if we combine all of these nascent options together then we have a much cleaner API that’s easily extendable and is less likely to cause confusion in the future.


That’s it! I just wanted to make a quick note about those two lessons. Here’s my question for you: What have you learned when it comes to making components or working on design systems?

The post Two Lessons I Learned From Making React Components appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Domain-Driven Design With React

There is very little guidance on how to organize front-end applications in the world of React. (Just move files around until it “feels right,” lol). The truth is that we can do better. Let’s take a look at one pattern you might consider using to architect your site.

At first, you might split up your code between /components and /containers folders. This works for small sites but you’ll find yourself look for something more robust when scaling to larger sites. Luckily, decades of research into systems design has given us a wealth of patterns to explore to create a scalable architecture.

One of those is domain-driven design, and it has regained popularity over the last few years. Let’s explore how we can use it in React-land.

A primer on domain-driven design

Domain-driven design (DDD) is the practice of managing complexity of software applications by relating their underlying data models to domain logic. That’s a mouthful, so let’s break it down further.

The domain is an ontology, meaning how things are grouped in the world. For example, the word joist has a very specific connection to the domain of building construction. Another word, like Mike, can belong to multiple domains, such as the domain of Biblical names (short for Michael), or in the domain of politics as it relates to the NATO phonetic alphabet.

When the design is domain-driven, it means we place the model of our domain (e.g. a playing card in the domain of Poker) in a context (e.g. the contextual grouping, such as a Game) to help manage the complexity.

Organizing a DDD site

Domain-driven design is specifically for handling the complexity of growing sites as they add more and more models. It doesn’t really make sense for a site with one model. Once you get to about four models, that’s a good time to start looking at binding your models to multiple contexts. This isn’t a hard-and-fast rule, so don’t feel like you have to break out into multiple contexts, but once you get above four models, those contextual groupings will begin to surface.

Start by organizing your domains

Let’s use Twitter as our example site to organize. One way to separate domains within Twitter is to split up our models between the Blog platform that powers the Tweets and the Interaction elements that enable the micro-blogging to spread and flourish.

Is this the only way to separate concerns in Twitter? Definitely not! One key aspect of DDD is that there is no one correct way to create domains. There are plenty of ways to split up the bounded contexts of an application, so don’t focus too much on the architecture we’ve chosen. Instead, use this as a springboard to understand how we can apply DDD to the organization of our front-end code.

That said, our code will now be structured to look like this (assuming you start with something like create-react-app):

twitter/ ├── App.css ├── App.js ├── App.test.js ├── blog/ └── interaction/

Define the components and containers in each domain

Now that we have our basic folder structure set up, it is time to add some real components! Looking at our domain UML diagram above, it would be useful to start with containers that fetch data on a given page and components that organize the templates that compose those pages. Expanding on our app, we now have the following structure in place (omitting our accompanying test.js files for simplicity):

twitter/ ├── App.css ├── App.js ├── App.test.js ├── blog/ │   ├── HomePage.js │   ├── TweetCard.js │   ├── TweetDialog.js │   ├── TweetList.js │   ├── TweetListItem.js │   ├── UserPage.js │   └── UserCard.js └── interaction/     ├── FollowButton.js     ├── LikeButton.js     └── ShareButton.js

We still keep our App file to initialize React to our root-level HTML tag. With our domains in place, we begin to build our containers (such as HomePage and UserPage) and components (such as TweetCard and TweetListItem). Alternatively, we could further segment the models within our domains to look like so:

twitter/ └── blog/     ├── user/     │   ├── HomePage.js     │   ├── UserCard.js     │   └── UserPage.js     └── tweet/         ├── TweetCard.js         ├── TweetDialog.js         ├── TweetList.js         └── TweetListItem.js

But given the size of the application it isn’t necessary at this stage.

Add helpers, if they’re needed

As we build out our application our UIs will continue to increase in complexity. To deal with this, we have two methods for separating concerns and pulling logic out of our component templates: presenters and utilities. Presenters push all of the visual presentation logic out of the templates to keep the view layer as clean and simple as possible. Utilities collect shared functionality for all other logic on the front end that is not specifically related to the templates. Let’s examine these a bit closer.

Clean up templates with presenters

Think of a Twitter profile. What kinds of elements do you see here on my account?

There’s information directly related to my user: name, handle, description, location, website, birthday, start date. There are also counts of associations between other models — how many other users are following me? How many other users am I following? There’s additional logic that isn’t even captured on the page, such as my tweets, replies, media uploads, and content I’ve liked. To capture all of this presentational logic appropriately, we can add an additional file within our file tree to isolate our presenter pattern from the JSX component:

twitter/ └── blog/     ├── user/     │   ├── UserCard.js     │   ├── UserCard.presenter.js

Push out logic into utilities

Certain presentational logic is so fundamental that it could be useful across applications regardless of whether or not it is used within rendering. Currency formatting, validations, and timestamp formatting are all use cases where we could benefit from isolated utility functions across our application. Where do those live? Since they span domains, utilities can be in their own folder:

    twitter/     ├── App.css     ├── App.js     ├── App.test.js     ├── blog/     │   ├── HomePage.js     │   ├── TweetCard.js     │   ├── TweetDialog.js     │   ├── TweetList.js     │   ├── TweetListItem.js     │   ├── UserCard.js     │   ├── UserCard.presenterjs     │   └── UserPage.js     ├── interaction/     │   ├── FollowButton.js     │   ├── LikeButton.js     │   └── ShareButton.js     └── utils/          ├── currency.js          ├── time.js          └── validation.js

There is no wrong way to organize your app!

Ultimately, the choice is yours. This is just one example for myriad ways you could arrange your application. Domain-driven design is a valuable tool because it separates business logic in a meaningful way, creates a clearer distinction for domain expertise amongst developers, and provides rules for easily organizing and scaling your code.

But if you’re looking for an alternative to the traditional chaos of React application file structures, take a look at domain-driven design. It may be just the thing.

Lastly, if you like this sort of content and want to learn more about front-end, user interface development, and UX design and research (organized by your experience in the industry), I run a free newsletter that you might want to check out.

The post Domain-Driven Design With React appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Testing React Hooks With Enzyme and React Testing Library

As you begin to make use of React hooks in your applications, you’ll want to be certain the code you write is nothing short of solid. There’s nothing like shipping buggy code. One way to be certain your code is bug-free is to write tests. And testing React hooks is not much different from how React applications are tested in general.

In this tutorial, we will look at how to do that by making use of a to-do application built with hooks. We’ll cover writing of tests using Ezyme and React Testing Library, both of which are able to do just that. If you’re new to Enzyme, we actually posted about it a little while back showing how it can be used with Jest in React applications. It’s not a bad idea to check that as we dig into testing React hooks.

Here’s what we want to test

A pretty standard to-do component looks something like this:

import React, { useState, useRef } from "react"; const Todo = () => {   const [todos, setTodos] = useState([     { id: 1, item: "Fix bugs" },     { id: 2, item: "Take out the trash" }   ]);   const todoRef = useRef();   const removeTodo = id => {     setTodos(todos.filter(todo => todo.id !== id));   };   const addTodo = data => {     let id = todos.length + 1;     setTodos([       ...todos,       {         id,         item: data       }     ]);   };   const handleNewTodo = e => {     e.preventDefault();     const item = todoRef.current;     addTodo(item.value);     item.value = "";   };   return (     <div className="container">       <div className="row">         <div className="col-md-6">           <h2>Add Todo</h2>         </div>       </div>       <form>         <div className="row">           <div className="col-md-6">             <input               type="text"               autoFocus               ref={todoRef}               placeholder="Enter a task"               className="form-control"               data-testid="input"             />           </div>         </div>         <div className="row">           <div className="col-md-6">             <button               type="submit"               onClick={handleNewTodo}               className="btn btn-primary"             >               Add Task             </button>           </div>         </div>       </form>       <div className="row todo-list">         <div className="col-md-6">           <h3>Lists</h3>           {!todos.length ? (             <div className="no-task">No task!</div>           ) : (             <ul data-testid="todos">               {todos.map(todo => {                 return (                   <li key={todo.id}>                     <div>                       <span>{todo.item}</span>                       <button                         className="btn btn-danger"                         data-testid="delete-button"                         onClick={() => removeTodo(todo.id)}                       >                         X                       </button>                     </div>                   </li>                 );               })}             </ul>           )}         </div>       </div>     </div>   ); }; export default Todo; 

Testing with Enzyme

We need to install the packages before we can start testing. Time to fire up the terminal!

npm install --save-dev enzyme enzyme-adapter-16 

Inside the src directory, create a file called setupTests.js. This is what we’ll use to configure Enzyme’s adapter.

import Enzyme from "enzyme"; import Adapter from "enzyme-adapter-react-16"; Enzyme.configure({ adapter: new Adapter() }); 

Now we can start writing our tests! We want to test four things:

  1. That the component renders
  2. That the initial to-dos get displayed when it renders
  3. That we can create a new to-do and get back three others
  4. That we can delete one of the initial to-dos and have only one to-do left

In your src directory, create a folder called __tests__ and create the file where you’ll write your Todo component’s tests in it. Let’s call that file Todo.test.js.

With that done, we can import the packages we need and create a describe block where we’ll fill in our tests.

import React from "react"; import { shallow, mount } from "enzyme"; import Todo from "../Todo";  describe("Todo", () => {   // Tests will go here using `it` blocks });

Test 1: The component renders

For this, we’ll make use of shallow render. Shallow rendering allows us to check if the render method of the component gets called — that’s what we want to confirm here because that’s the proof we need that the component renders.

it("renders", () => {   shallow(<Todo />); });

Test 2: Initial to-dos get displayed

Here is where we’ll make use of the mount method, which allows us to go deeper than what shallow gives us. That way, we can check the length of the to-do items.

it("displays initial to-dos", () => {   const wrapper = mount(<Todo />);   expect(wrapper.find("li")).toHaveLength(2); });

Test 3: We can create a new to-do and get back three others

Let’s think about the process involved in creating a new to-do:

  1. The user enters a value into the input field.
  2. The user clicks the submit button.
  3. We get a total of three to-do items, where the third is the newly created one.
it("adds a new item", () => {   const wrapper = mount(<Todo />);   wrapper.find("input").instance().value = "Fix failing test";   expect(wrapper.find("input").instance().value).toEqual("Fix failing test");   wrapper.find('[type="submit"]').simulate("click");   expect(wrapper.find("li")).toHaveLength(3);   expect(     wrapper       .find("li div span")       .last()       .text()   ).toEqual("Fix failing test"); });

We mount the component then we make use of find() and instance() methods to set the value of the input field. We assert that the value of the input field is set to “Fix failing test” before going further to simulate a click event, which should add the new item to the to-do list.

We finally assert that we have three items on the list and that the third item is equal to the one we created.

Test 4: We can delete one of the initial to-dos and have only one to-do left

it("removes an item", () => {   const wrapper = mount(<Todo />);   wrapper     .find("li button")     .first()     .simulate("click");   expect(wrapper.find("li")).toHaveLength(1);   expect(wrapper.find("li span").map(item => item.text())).toEqual([     "Take out the trash"   ]); });

In this scenario, we return the to-do with a simulated click event on the first item. It’s expected that this will call the removeTodo() method, which should delete the item that was clicked. Then we’re checking the numbers of items we have, and the value of the one that gets returned.

The source code for these four tests are here on GitHub for you to check out.

Testing With react-testing-library

We’ll write three tests for this:

  1. That the initial to-do renders
  2. That we can add a new to-do
  3. That we can delete a to-do

Let’s start by installing the packages we need:

npm install --save-dev @testing-library/jest-dom @testing-library/react

Next, we can import the packages and files:

import React from "react"; import { render, fireEvent } from "@testing-library/react"; import Todo from "../Todo"; import "@testing-library/jest-dom/extend-expect";  test("Todo", () => {   // Tests go here }

Test 1: The initial to-do renders

We’ll write our tests in a test block. The first test will look like this:

it("displays initial to-dos", () => {   const { getByTestId } = render(<Todo />);   const todos = getByTestId("todos");   expect(todos.children.length).toBe(2); });

What’s happening here? We’re making use of getTestId to return the node of the element where data-testid matches the one that was passed to the method. That’s the <ul> element in this case. Then, we’re checking that it has a total of two children (each child being a <li> element inside the unordered list). This will pass as the initial to-do is equal to two.

Test 2: We can add a new to-do

We’re also making use of getTestById here to return the node that matches the argument we’re passing in.

it("adds a new to-do", () => {   const { getByTestId, getByText } = render(<Todo />);   const input = getByTestId("input");   const todos = getByTestId("todos");   input.value = "Fix failing tests";   fireEvent.click(getByText("Add Task"));   expect(todos.children.length).toBe(3); });

We use getByTestId to return the input field and the ul element like we did before. To simulate a click event that adds a new to-do item, we’re using fireEvent.click() and passing in the getByText() method, which returns the node whose text matches the argument we passed. From there, we can then check to see the length of the to-dos by checking the length of the children array.

Test 3: We can delete a to-do

This will look a little like what we did a little earlier:

it("deletes a to-do", () => {   const { getAllByTestId, getByTestId } = render(<Todo />);   const todos = getByTestId("todos");   const deleteButton = getAllByTestId("delete-button");   const first = deleteButton[0];   fireEvent.click(first);   expect(todos.children.length).toBe(1); });

We’re making use of getAllByTestId to return the nodes of the delete button. Since we only want to delete one item, we fire a click event on the first item in the collection, which should delete the first to-do. This should then make the length of todos children equal to one.

These tests are also available on GitHub.

Linting

There are two lint rules to abide by when working with hooks:

Rule 1: Call hooks at the top level

…as opposed to inside conditionals, loops or nested functions.

// Don't do this! if (Math.random() > 0.5) {   const [invalid, updateInvalid] = useState(false); }

This goes against the first rule. According to the official documentation, React depends on the order in which hooks are called to associate state and the corresponding useState call. This code breaks the order as the hook will only be called if the conditions are true.

This also applies to useEffect and other hooks. Check out the documentation for more details.

Rule 2: Call hooks from React functional components

Hooks are meant to be used in React functional components — not in React’s class component or a JavaScript function.

We’ve basically covered what not to do when it comes to linting. We can avoid these missteps with an npm package that specifically enforces these rules.

npm install eslint-plugin-react-hooks --save-dev

Here’s what we add to the package’s configuration file to make it do its thing:

{   "plugins": [     // ...     "react-hooks"   ],   "rules": {     // ...     "react-hooks/rules-of-hooks": "error",     "react-hooks/exhaustive-deps": "warn"   } }

If you are making use of Create React App, then you should know that the package supports the lint plugin out of the box as of v3.0.0.

Go forth and write solid React code!

React hooks are equally prone to error as anything else in your application and you’re gonna want to ensure that you use them well. As we just saw, there’s a couple of ways we can go about it. Whether you use Enzyme or You can either make use of enzyme or React Testing Library to write tests is totally up to you. Either way, try making use of linting as you go, and no doubt, you’ll be glad you did.

The post Testing React Hooks With Enzyme and React Testing Library appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Build your own React

Wowza! Rodrigo Pombo’s article about how to build React from scratch is fantastic, not only because it’s well written, but because of the outstanding interaction design: each line in the code examples ge highlighted and explored in further detail as you scroll down the page.

This makes it super easy to walk through each process step by step:

How neat is that? This definitely feels like a new way we should consider showing technical information I reckon as it’s just so much easier to read the code.

Oh, and the part about building your own React? Andy Bell advocates something similar when it comes to state management:

Libraries like Redux, MobX and Vuex make managing cross-component state almost trivial. This is great for an application’s resilience and it works really well with a state-first, reactive framework such as React or Vue.

How do these libraries work though? What would it take to write one ourselves? Turns out, it’s pretty straightforward and there’s an opportunity to learn some really common patterns and also learn about some useful modern APIs that are available to us.

Seems like Rodrigo’s pattern is a nice way to make that sort of learning possible.

Direct Link to ArticlePermalink

The post Build your own React appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Working with Fusebox and React

If you are searching for an alternative bundler to webpack, you might want to take a look at FuseBox. It builds on what webpack offers — code-splitting, hot module reloading, dynamic imports, etc. — but code-splitting in FuseBox requires zero configuration by default (although webpack will offer the same as of version 4.0).

Instead, FuseBox is built for simplicity (in the form of less complicated configuration) and performance (by including aggressive caching methods). Plus, it can be extended to use tons of plugins that can handle anything you need above and beyond the defaults.

Oh yeah, and if you are a fan of TypeScript, you might be interested in knowing that FuseBox makes it a first-class citizen. That means you can write an application in Typescript — with no configuration! — and it will use the Typescript transpiler to compile scripts by default. Don’t plan on using Typescript? No worries, the transpiler will handle any JavaScript. Yet another bonus!

To illustrate just how fast it is to to get up and running, let’s build the bones of an application with create-react-app. Everything we’re doing will be on GitHub if you want to follow along.

FuseBox is not the only alternative to webpack, of course. There are plenty and, in fact, Maks Akymenko has a great write-up on Parcel which is another great alternative worth looking into.

The basic setup

Start by creating a new project directory and initializing it with npm:

## Create the directory mkdir csstricks-fusebox-react && $  _ ## Initialize with npm default options npm init -y

Now we can install some dependencies. We’re going to build the app in React, so we’ll need that as well as react-dom.

npm install --save react react-dom

Next, we’ll install FuseBox and Typescript as dependencies. We’ll toss Uglify in there as well for help minifying our scripts and add support for writing styles in Sass.

npm install --save-dev fuse-box typescript uglify-js node-sass

Alright, now let’s create a src folder in the root of the project directory (which can be done manually). Add the following files (`app.js and index.js) in there, including the contents:

// App.js  import * as React from "react"; import * as logo from "./logo.svg";  const App = () => {   return (     <div className="App">       <header className="App-header">         <img src={logo} className="App-logo" alt="logo" />         <h1 className="App-title">Welcome to React</h1>       </header>       <p className="App-intro">         To get started, edit `src/App.js` and save to reload.       </p>     </div>   ) };  export default App;

You may have noticed that we’re importing an SVG file. You can download it directly from the GitHub repo.

// index.js  import * as React from "react"; import * as ReactDOM from "react-dom"; import App from "./App"  ReactDOM.render(   <App />, document.getElementById('root') );

You can see that the way we handle importing files is a little different than a typical React app. That’s because FuseBox does not polyfill imports by default.

So, instead of doing this:

import React from "react";

…we’re doing this:

import * as React from "react";
<!-- ./src/index.html -->  <!DOCTYPE html> <html lang="en">   <head>     <title>CSSTricks Fusebox React</title>     $  css   </head>    <body>     <noscript>       You need to enable JavaScript to run this app.     </noscript>     <div id="root"></div>     $  bundles   </body> </html>

Styling isn’t really the point of this post, but let’s drop some in there to dress things up a bit. We’ll have two stylesheets. The first is for the App component and saved as App.css.

/* App.css */  .App {   text-align: center; }  .App-logo {   animation: App-logo-spin infinite 20s linear;   height: 80px; }  .App-header {   background-color: #222;   height: 150px;   padding: 20px;   color: white; }  .App-intro {   font-size: large; }  @keyframes App-logo-spin {   from {     transform: rotate(0deg);   }   to {     transform:         rotate(360deg);   } }

The second stylesheet is for index.js and should be saved as index.css:

/* index.css */ body {   margin: 0;   padding: 0;   font-family: sans-serif; }

OK, we’re all done with the initial housekeeping. On to extending FuseBox with some goodies!

Plugins and configuration

We said earlier that configuring FuseBox is designed to be way less complex than the likes of webpack — and that’s true! Create a file called fuse.js in the root directory of the application.

We start with importing the plugins we’ll be making use of, all the plugins come from the FuseBox package we installed.

const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");

Next, we’ll initialize a FuseBox instance and tell it what we’re using as the home directory and where to put compiled assets:

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js" });

We’ll let FuzeBox know that we intend to use the TypeScript compiler:

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true, });

We identified plugins in the first line of the configuration file, but now we’ve got to call them. We’re using the plugins pretty much as-is, but definitely check out what the CSSPlugin, SVGPlugin and WebIndexPlugin have to offer if you want more fine-grained control over the options.

const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [ // HIGHLIGHT     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] });  const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");  const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] }); fuse.dev(); fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch()  fuse.run();

FuseBox lets us configure a development server. We can define ports, SSL certificates, and even open the application in a browser on build.

We’ll simply use the default environment for this example:

fuse.dev();

It is important to define the development environment *before* the bundle instructions that come next:

fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch().

What the heck is this? When we initialized the FuseBox instance, we specified an output using dist/$ name.js. The value for $ name is provided by the bundle() method. In our case, we set the value as app. That means that when the application is bundled, the output destination will be dist/app.js.

The instructions() method defines how FuseBox should deal with the code. In our case, we’re telling it to start with index.js and to execute it after it’s loaded.

The hmr() method is used for cases where we want to update the user when a file changes, this usually involves updating the browser when a file changes. Meanwhile, watch() re-bundles the bundled code after every saved change.

With that, we’ll cap it off by launching the build process with fuse.run() at the end of the configuration file. Here’s everything we just covered put together:

const { FuseBox, CSSPlugin, SVGPlugin, WebIndexPlugin } = require("fuse-box");  const fuse = FuseBox.init({   homeDir: "src",   output: "dist/$  name.js",   useTypescriptCompiler: true,   plugins: [     CSSPlugin(),     SVGPlugin(),     WebIndexPlugin({       template: "src/index.html"     })   ] }); fuse.dev(); fuse   .bundle("app")   .instructions(`>index.js`)   .hmr()   .watch()  fuse.run();

Now we can run the application from the terminal by running node fuse. This will start the build process which creates the dist folder that contains the bundled code and the template we specified in the configuration. After the build process is done, we can point the browser to http://localhost:4444/ to see our app.

Running tasks with Sparky

FuseBox includes a task runner that can be used to automate a build process. It’s called Sparky and you can think of it as sorta like Grunt and Gulp, the difference being that it is built on top of FuseBox with built-in access to FuseBox plugins and the FuseBox API.

We don’t have to use it, but task runners make development a lot easier by automating things we’d otherwise have to do manually and it makes sense to use what’s specifically designed for FuseBox.

To use it, we’ll update the configuration we have in fuse.js, starting with some imports that go at the top of the file:

const { src, task, context } = require("fuse-box/sparky");

Next, we’ll define a context, which will look similar to what we already have. We’re basically wrapping what we did in a context and setConfig(), then initializing FuseBox in the return:

context({   setConfig() {     return FuseBox.init({       homeDir: "src",       output: "dist/$  name.js",       useTypescriptCompiler: true,       plugins: [         CSSPlugin(),         SVGPlugin(),         WebIndexPlugin({           template: "src/index.html"         })       ]     });   },   createBundle(fuse) {     return fuse       .bundle("app")       .instructions(`> index.js`)       .hmr();   } });

It’s possible to pass a class, function or plain object to a context. In the above scenario, we’re passing functions, specifically setConfig() and createBundle(). setConfig() initializes FuseBox and sets up the plugins. createBundle() does what you might expect by the name, which is bundling the code. Again, the difference from what we did before is that we’re embedding both functionalities into different functions which are contained in the context object.

We want our task runner to run tasks, right? Here are a few examples we can define:

task("clean", () => src("dist").clean("dist").exec()); task("default", ["clean"], async (context) => {   const fuse = context.setConfig();   fuse.dev();   context.createBundle(fuse);   await fuse.run() });

The first task will be responsible for cleaning the dist directory. The first argument is the name of the task, while the second is the function that gets called when the task runs.
To call the first task, we can do node fuse clean from the terminal.

When a task is named default (which is the first argument as in the second task), that task will be the one that gets called by default when running node fuse — in this case, that’s the second task in our configuration. Other tasks need to be will need to be called explicitly in terminal, like node fuse <task_name>.

So, our second task is the default and three arguments are passed into it. The first is the name of the task (`default`), the second (["clean"]) is an array of dependencies that should be called before the task itself is executed, and the third is a function (fuse.dev()) that gets the initialized FuseBox instance and begins the bundling and build process.

Now we can run things with node fuse in the terminal. You have the option to add these to your package.json file if that’s more comfortable and familiar to you. The script section would look like this:

"scripts": {   "start": "node fuse",   "clean": "node fuse clean" },

That’s a wrap!

All in all, FuseBox is an interesting alternative to webpack for all your application bundling needs. As we saw, it offers the same sort of power that we all tend to like about webpack, but with a way less complicated configuration process that makes it much easier to get up and running, thanks to built-in Typescript support, performance considerations, and a task runner that’s designed to take advantage of the FuseBox API.

What we look at was a pretty simple example. In practice, you’re likely going to be working with more complex applications, but the concepts and principles are the same. It’s nice to know that FuseBox is capable of handling more than what’s baked into it, but that the initial setup is still super streamlined.

If you’re looking for more information about FuseBox, it’s site and documentation are obviously great starting point. the following links are also super helpful to get more perspective on how others are setting it up and using it on projects.

The post Working with Fusebox and React appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]