Tag: React

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

, , , ,

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]

It’s All In the Head: Managing the Document Head of a React Powered Site With React Helmet

The document head might not be the most glamorous part of a website, but what goes into it is arguably just as important to the success of your website as its user interface. This is, after all, where you tell search engines about your website and integrate it with third-party applications like Facebook and Twitter, not to mention the assets, ranging from analytics libraries to stylesheets, that you load and initialize there.

A React application lives in the DOM node it was mounted to, and with this in mind, it is not at all obvious how to go about keeping the contents of the document head synchronized with your routes. One way might be to use the componentDidMount lifecycle method, like so:

componentDidMount() {   document.title = "Whatever you want it to be"; }

However, you are not just going to want to change the title of the document, you are also going to want to modify an array of meta and other tags, and it will not be long before you conclude that managing the contents of the document head in this manner gets tedious pretty quickly and prone to error, not to mention that the code you end up with will be anything but semantic. There clearly has to be a better way to keep the document head up to date with your React application. And as you might suspect given the subject matter of this tutorial, there is a simple and easy to use component called React Helmet, which was developed by and is maintained by the National Football League(!).

In this tutorial, we are going to explore a number of common use cases for React Helmet that range from setting the document title to adding a CSS class to the document body. Wait, the document body? Was this tutorial not supposed to be about how to work with the document head? Well, I have got good news for you: React Helmet also lets you work with the attributes of the <html> and <body> tags; and it goes without saying that we have to look into how to do that, too!

View Repo

One important caveat of this tutorial is that I am going to ask you to install Gatsby — a static site generator built on top of React — instead of Create React App. That’s because Gatsby supports server side rendering (SSR) out of the box, and if we truly want to leverage the full power of React Helmet, we will have to use SSR!

Why, you might ask yourself, is SSR important enough to justify the introduction of an entire framework in a tutorial that is about managing the document head of a React application? The answer lies in the fact that search engine and social media crawlers do a very poor job of crawling content that is generated through asynchronous JavaScript. That means, in the absence of SSR, it will not matter that the document head content is up to date with the React application, since Google will not know about it. Fortunately, as you will find out, getting started with Gatsby is no more complicated than getting started with Create React App. I feel quite confident in saying that if this is the first time you have encountered Gatsby, it will not be your last!

Getting started with Gatsby and React Helmet

As is often the case with tutorials like this, the first thing we will do is to install the dependencies that we will be working with.

Let us start by installing the Gatsby command line interface:

npm i -g gatsby-cli

While Gatsby’s starter library contains a plethora of projects that provide tons of built-in features, we are going to restrict ourselves to the most basic of these starter projects, namely the Gatsby Hello World project.

Run the following from your Terminal:

gatsby new my-hello-world-starter https://github.com/gatsbyjs/gatsby-starter-hello-world

my-hello-world-starter is the name of your project, so if you want to change it to something else, do so by all means!

Once you have installed the starter project, navigate into its root directory by running cd [name of your project]/ from the Terminal, and once there, run gatsby develop. Your site is now running at http://localhost:8000, and if you open and edit src/pages/index.js, you will notice that your site is updated instantaneously: Gatsby takes care of all our hot-reloading needs without us even having to think of — and much less touch — a webpack configuration file. Just like Create React App does! While I would recommend all JavaScript developers learn how to set up and configure a project with webpack for a granular understanding of how something works, it sure is nice to have all that webpack boilerplate abstracted away so that we can focus our energy on learning about React Helmet and Gatsby!

Next up, we are going to install React Helmet:

npm i --save react-helmet

After that, we need to install Gatsby Plugin React Helmet to enable server rendering of data added with React Helmet:

npm i --save gatsby-plugin-react-helmet

When you want to use a plugin with Gatsby, you always need to add it to the plugins array in the gatsby-config.js file, which is located at the root of the project directory. The Hello World starter project does not ship with any plugins, so we need to make this array ourselves, like so:

module.exports = {   plugins: [`gatsby-plugin-react-helmet`] }

Great! All of our dependencies are now in place, which means we can move on to the business end of things.

Our first foray with React Helmet

The first question that we need to answer is where React Helmet ought to live in the application. Since we are going to use React Helmet on all of our pages, it makes sense to nest it in a component together with the page header and footer components since they will also be used on every page of our website. This component will wrap the content on all of our pages. This type of component is commonly referred to as a “layout” component in React parlance.

In the src directory, create a new directory called components in which you create a file called layout.js. Once you have done this, copy and paste the code below into this file.

import React from "react" import Helmet from "react-helmet"  export default ({ children }) => (   <>     <Helmet>       <title>Cool</title>     </Helmet>     <div>       <header>         <h1></h1>         <nav>           <ul>           </ul>         </nav>         </header>       {children}       <footer>{`$  {new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>     </div>   </> )

Let’s break down that code.

First off, if you are new to React, you might be asking yourself what is up with the empty tags that wrap the React Helmet component and the header and footer elements. The answer is that React will go bananas and throw an error if you try to return multiple elements from a component, and for a long time, there was no choice but to nest elements in a parent element — commonly a div — which led to a distinctly unpleasant element inspector experience littered with divs that serve no purpose whatsoever. The empty tags, which are a shorthand way for declaring the Fragment component, were introduced to React as a solution to this problem. They let us return multiple elements from a component without adding unnecessary DOM bloat.

That was quite a detour, but if you are like me, you do not mind a healthy dose of code-related trivia. In any case, let us move on to the <Helmet> section of the code. As you are probably able to deduce from a cursory glance, we are setting the title of the document here, and we are doing it in exactly the same way we would in a plain HTML document; quite an improvement over the clunky recipe I typed up in the introduction to this tutorial! However, the title is hard coded, and we would like to be able to set it dynamically. Before we take a look at how to do that, we are going to put our fancy Layout component to use.

Head over to src/pages/ and open ìndex.js. Replace the existing code with this:

import React from "react" import Layout from "../components/layout"  export default () =>    <Layout>     <div>I live in a layout component, and life is pretty good here!</div>   </Layout>

That imports the Layout component to the application and provides the markup for it.

Making things dynamic

Hard coding things in React does not make much sense because one of the major selling points of React is that makes it’s easy to create reusable components that are customized by passing props to them. We would like to be able to use props to set the title of the document, of course, but what exactly do we want the title to look like? Normally, the document title starts with the name of the website, followed by a separator and ends with the name of the page you are on, like Website Name | Page Name or something similar. You are probably right, in thinking, we could use template literals for this, and right you are!

Let us say that we are creating a website for a company called Cars4All. In the code below, you will see that the Layout component now accepts a prop called pageTitle, and that the document title, which is now rendered with a template literal, uses it as a placeholder value. Setting the title of the document does not get any more difficult than that!

import React from "react" import Helmet from "react-helmet"  export default ({ pageTitle, children }) => (   <>     <Helmet>       <title>{`Cars4All | $  {pageTitle}`}</title>     </Helmet>     <div>       <header>         <h1>Cars4All</h1>         <nav>           <ul>           </ul>         </nav>         </header>       {children}       <footer>{`$  {new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>     </div>   </> )

Let us update ìndex.js accordingly by setting the pageTitle to “Home”:

import React from "react" import Layout from "../components/layout"  export default () =>    <Layout pageTitle="Home">     <div>I live in a layout component, and life is pretty good here!</div>   </Layout>

If you open http://localhost:8000 in the browser, you will see that the document title is now Cars4All | Home. Victory! However, as stated in the introduction, we will want to do more in the document head than set the title. For instance, we will probably want to include charset, description, keywords, author and viewport meta tags.

How would we go about doing that? The answer is exactly the same way we set the title of the document:

import React from "react" import Helmet from "react-helmet"  export default ({ pageMeta, children }) => (   <>     <Helmet>       <title>{`Cars4All | $  {pageMeta.title}`}</title>              {/* The charset, viewport and author meta tags will always have the same value, so we hard code them! */}       <meta charset="UTF-8" />       <meta name="viewport" content="width=device-width, initial-scale=1.0" />       <meta name="author" content="Bob Trustly" />        {/* The rest we set dynamically with props */}       <meta name="description" content={pageMeta.description} />              {/* We pass an array of keywords, and then we use the Array.join method to convert them to a string where each keyword is separated by a comma */}       <meta name="keywords" content={pageMeta.keywords.join(',')} />     </Helmet>     <div>       <header>         <h1>Cars4All</h1>         <nav>           <ul>           </ul>         </nav>         </header>       {children}       <footer>{`$  {new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>     </div>   </> )

As you may have noticed, the Layout component no longer accepts a pageTitle prop, but a pageMeta one instead, which is an object that encapsulates all the meta data on a page. You do not have to do bundle all the page data like this, but I am very averse to props bloat. If there is data with a common denominator, I will always encapsulate it like this. Regardless, let us update index.js with the relevant data:

import React from "react" import Layout from "../components/layout"  export default () =>    <Layout     pageMeta={{       title: "Home",       keywords: ["cars", "cheap", "deal"],       description: "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!"     }}   >     <div>I live in a layout component, and life is pretty good here!</div>   </Layout>

If you open http://localhost:8000 again, fire up DevTools and dive into the document head, you will see that all of the meta tags we added are there. Regardless of whether you want to add more meta tags, a canonical URL or integrate your site with Facebook using the Open Graph Protocol, this is how you about about it. One thing that I feel is worth pointing out: if you need to add a script to the document head (maybe because you want to enhance the SEO of your website by including some structured data), then you have to render the script as a string within curly braces, like so:

<script type="application/ld+json">{` {   "@context": "http://schema.org",   "@type": "LocalBusiness",   "address": {   "@type": "PostalAddress",   "addressLocality": "Imbrium",   "addressRegion": "OH",   "postalCode":"11340",   "streetAddress": "987 Happy Avenue"   },   "description": "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!",   "name": "Cars4All",   "telephone": "555",   "openingHours": "Mo,Tu,We,Th,Fr 09:00-17:00",   "geo": {   "@type": "GeoCoordinates",   "latitude": "40.75",   "longitude": "73.98"   }, 			   "sameAs" : ["http://www.facebook.com/your-profile",   "http://www.twitter.com/your-profile",   "http://plus.google.com/your-profile"] } `}</script>

For a complete reference of everything that you can put in the document head, check out Josh Buchea’s great overview.

The escape hatch

For whatever reason, you might have to overwrite a value that you have already set with React Helmet — what do you do then? The clever people behind React Helmet have thought of this particular use case and provided us with an escape hatch: values set in components that are further down the component tree always take precedence over values set in components that find themselves higher up in the component tree. By taking advantage of this, we can overwrite existing values.

Say we have a fictitious component that looks like this:

import React from "react" import Helmet from "react-helmet"  export default () => (   <>     <Helmet>       <title>The Titliest Title of Them All</title>     </Helmet>     <h2>I'm a component that serves no real purpose besides mucking about with the document title.</h2>   </> )

And then we want to include this component in ìndex.js page, like so:

import React from "react" import Layout from "../components/layout" import Fictitious from "../components/fictitious"  export default () =>    <Layout     pageMeta={{       title: "Home",       keywords: ["cars", "cheap", "deal"],       description: "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!"     }}   >     <div>I live in a layout component, and life is pretty good here!</div>     <Fictitious />   </Layout>

Because the Fictitious component hangs out in the underworld of our component tree, it is able to hijack the document title and change it from “Home” to “The Titliest Title of Them All.” While I think it is a good thing that this escape hatch exists, I would caution against using it unless there really is no other way. If other developers pick up your code and have no knowledge of your Fictitious component and what it does, then they will probably suspect that the code is haunted, and we do not want to spook our fellow developers! After all, fighter jets do come with ejection seats, but that is not to say fighter pilots should use them just because they can.

Venturing outside of the document head

As mentioned earlier, we can also use React Helmet to change HTML and body attributes. For example, it’s always a good idea to declare the language of your website, which you do with the HTML lang attribute. That’s set with React Helmet like this:

<Helmet>    /* Setting the language of your page does not get more difficult than this! */   <html lang="en" />        /* Other React Helmet-y stuff...  */ </Helmet>

Now let us really tap into the power of React Helmet by letting the pageMeta prop of the Layout component accept a custom CSS class that is added to the document body. Thus far, our React Helmet work has been limited to one page, so we can really spice things up by creating another page for the Cars4All site and pass a custom CSS class with the Layout component’s pageMeta prop.

First, we need to modify our Layout component. Note that since our Cars4All website will now consist of more than one page, we need to make it possible for site visitors to navigate between these pages: Gatsby’s Link component to the rescue!

Using the Link component is no more difficult than setting its to prop to the name of the file that makes up the page you want to link to. So if we want to create a page for the cars sold by Cars4All and we name the page file cars.js, linking to it is no more difficult than typing out <Link to="/cars/">Our Cars</Link>. When you are on the Our Cars page, it should be possible to navigate back to the ìndex.js page, which we call Home. That means we need to add <Link to="/">Home</Link> to our navigation as well.

In the new Layout component code below, you can see that we are importing the Link component from Gatsby and that the previously empty unordered list in the head element is now populated with the links for our pages. The only thing left to do in the Layout component is add the following snippet:

<body className={pageMeta.customCssClass ? pageMeta.customCssClass : ''}/>

…to the <Helmet> code, which adds a CSS class to the document body if one has been passed with the pageMeta prop. Oh, and given that we are going to pass a CSS class, we do, of course, have to create one. Let’s head back to the src directory and create a new directory called css in which we create a file called main.css. Last, but not least, we have to import it into the Layout component, because otherwise our website will not know that it exists. Then add the following CSS to the file:

.slick {   background-color: yellow;   color: limegreen;   font-family: "Comic Sans MS", cursive, sans-serif; }

Now replace the code in src/components/layout.js with the new Layout code that we just discussed:

import React from "react" import Helmet from "react-helmet" import { Link } from "gatsby" import "../css/main.css"  export default ({ pageMeta, children }) => (   <>     <Helmet>       {/* Setting the language of your page does not get more difficult than this! */}       <html lang="en" />             {/* Add the customCssClass from our pageMeta prop to the document body */}            <body className={pageMeta.customCssClass ? pageMeta.customCssClass : ''}/>              <title>{`Cars4All | $  {pageMeta.title}`}</title>              {/* The charset, viewport and author meta tags will always have the same value, so we hard code them! */}       <meta charset="UTF-8" />       <meta name="viewport" content="width=device-width, initial-scale=1.0" />       <meta name="author" content="Bob Trustly" />        {/* The rest we set dynamically with props */}       <meta name="description" content={pageMeta.description} />              {/* We pass an array of keywords, and then we use the Array.join method to convert them to a string where each keyword is separated by a comma */}       <meta name="keywords" content={pageMeta.keywords.join(',')} />     </Helmet>     <div>       <header>         <h1>Cars4All</h1>         <nav>           <ul>             <li><Link to="/">Home</Link></li>             <li><Link to="/cars/">Our Cars</Link></li>           </ul>         </nav>         </header>       {children}       <footer>{`$  {new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>     </div>   </> )

We are only going to add a custom CSS class to the document body in the cars.js page, so there is no need to make any modifications to the ìndex.js page. In the src/pages/ directory, create a file called cars.js and add the code below to it.

import React from "react" import Layout from "../components/layout"  export default () =>    <Layout     pageMeta={{       title: "Our Cars",       keywords: <a href="">"toyota", "suv", "volvo"],       description: "We sell Toyotas, gas guzzlers and Volvos. If we don't have the car you would like, let us know and we will order it for you!!!",       customCssClass: "slick"     }}   >     <h2>Our Cars</h2>     <div>A car</div>     <div>Another car</div>     <div>Yet another car</div>     <div>Cars ad infinitum</div>   </Layout>

If you head on over to http://localhost:8000, you will see that you can now navigate between the pages. Moreover, when you land on the cars.js page, you will notice that something looks slightly off… Hmm, no wonder I call myself a web developer and not a web designer! Let’s open DevTools, toggle the document head and navigate back to the ìndex.js page. The content is updated when changing routes!

The icing on the cake

If you inspect the source of your pages, you might feel a tad bit cheated. I promised a SSR React website, but none of our React Helmet goodness can be found in the source.

What was the point of my foisting Gatsby on you, you might ask? Well, patience young padowan! Run gatsby build in Terminal from the root of the site, followed by gatsby serve.

Gatsby will tell you that the site is now running on http://localhost:9000. Dash over there and inspect the source of your pages again. Tadá, it’s all there! You now have a website that has all the advantages of a React SPA without giving up on SEO or integrating with third-party applications and what not. Gatsby is amazing, and it is my sincere hope that you will continue to explore what Gatsby has to offer.

On that note, happy coding!

The post It’s All In the Head: Managing the Document Head of a React Powered Site With React Helmet appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Get the Complete Intro to Web Development and Intro to React (with Hooks!) with Brian Holt 🎣

(This is a sponsored post.)

Hey, Marc here from Frontend Masters — excited to support CSS-Tricks ❤️!

Have you checked out Brian Holt’s courses yet? His most popular courses are the “Complete Intro” courses which give you the lay of the land in Web Development as well as the entire React ecosystem.

Complete Intro to Web Development, v2

This Complete Intro to Web Development assumes no prior coding knowledge, taking you from not knowing how websites are made to writing code for your own sites in HTML, CSS, JavaScript…all the way to building a server with Node.js!

You’ll learn to…

  • Setup the tools you need to write code.
  • Write HTML and CSS to put content on a websites and make them look aesthetically pleasing.
  • Make websites interactive with JavaScript, the de facto programming language of the web.
  • Use Git and other modern development tools through your computer’s terminal to save your work and pull in code libraries.
  • Use JavaScript via Node.js to serve your own website from a server.

Complete Intro to React, v5

In the Complete Intro to React course, you’ll start from the ground up, getting all the way to using the latest features in React, including hooks, effects, context, and portals. Throughout the course, you’ll piece together tools from the entire React ecosystem (like Reach Router) to build a full application to browse adoptable pets.

You’ll learn to…


  • Use the new hooks and effects methods to handle state in function components.

  • Understand how React works by writing React with and without JSX.

  • Package client-side applications with Parcel.

  • Leverage Prettier and ESLint to maintain high-quality code.

  • Route to pages and search results with Reach Router.


  • Grab API data and update state asynchronously in an effect.

Intermediate React, v2

The Intermediate React, v2 course is a modular course where you can pick and choose the various pieces of the react ecosystem you want to learn.

You’ll learn to…


  • Learn all the types of hooks, including useReducer, useCallback, useRef and more.

  • Make CSS local to your JavaScript components using the Emotion library.

  • Load your apps fast using code splitting, Suspense React and server-side rendering.

  • Use TypeScript for writing scalable apps with superior developer experience.

  • Redux for state management

  • Test your React applications using Jest.

You’ll love Brian’s awesome courses!

Join Now

Direct Link to ArticlePermalink

The post Get the Complete Intro to Web Development and Intro to React (with Hooks!) with Brian Holt 🎣 appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Demonstrating Reusable React Components in a Form

Components are the building blocks of React applications. It’s almost impossible to build a React application and not make use of components. It’s widespread to the point that some third-party packages provide you with components you can use to integrate functionality into your application.

These third-party components tend to be reusable. The difference between them and components you probably have in your application has to do with specificity.

Here’s what I mean. Say you run a company that sells polo shirts. There are two things you can do:

  1. You can produce polos that already have a design on them, or
  2. You can have the buyer choose the design they want.

Some fundamental things will be consistent, like all polos should be short-sleeved. But the user can pick variations of the shirts, such as the color and size. A short-sleeve polo would make a good React component in that case: it’s the same item with different variations.

Now let’s say you’re working on a login form. Like polo shirts, the form has consistent characteristics, but instead of size and color variations, we’d be looking at input fields, a submit button, perhaps even a lost password link. This can be componentized and implemented with variations in the inputs, buttons, links, etc.

Input Element Example

Let’s look at it from the perspective of creating an input field for your form. Here is how a typical text input will look like as a React component:

class Form extends React.Component {   this.state = {     username: ''   }      handleChange = (event) => {     this.setSate({ username: event.target.value })   }    render() {     return (       <input         name="username         type={type}         placeholder="Enter username"         onChange={this.handleChange}         value={this.state.username}       />     )   } }

To make this input element reusable in other places and projects, we’ll have to extract it into its own component. Let’s call it FormInput.

const FormInput = ({   name,   type,   placeholder,   onChange,   className,   value,   error,   children,   label,   ...props }) => {      return (     <React.Fragment>       <label htmlFor={name}>{label}</label>       <input         name={name}         type={type}         placeholder={placeholder}         onChange={onChange}         value={value}         className={className}         style={error && {border: 'solid 1px red'}}       />       { error && <p>{ error }</p>}     </React.Fragment>   ) }  FormInput.defaultProps = {   type: "text",   className: "" }  FormInput.propTypes = {   name: PropTypes.string.isRequired,   type: PropTypes.string,   placeholder: PropTypes.string.isRequired,   type: PropTypes.oneOf(['text', 'number', 'password']),   className: PropTypes.string,   value: PropTypes.any,   onChange: PropTypes.func.isRequired }

The component accepts certain props, such as the attributes that we need to make the input with valid markup, including the placeholder, value and name. We set up the input element in the render function, setting the attribute values as the props that are passed to the component. We even bind the input to a label to ensure they’re always together. You can see that we’re not making assumptions by predefining anything. The idea is to ensure that the component can be used in as many scenarios as possible.

This makes for a good component because it enforces good markup (something Brad Frost calls dumb React) which goes to show that not every component has to be some highly complex piece of functionality. Then again, if we were talking about something super basic, say a static heading, then reaching for a React component might be overkill. The possible yardstick for making something a reusable component probably ought to be when you need the same functionality in other parts of an application. There’s generally no need for a “reusable” component if that component is only used once.

We can make use of our input component in another component, the LoginPage.

class LoginPage extends React.Component {   state = {     user: {       username: "",       password: ""     },     errors: {},     submitted: false   };    handleChange = event => {     const { user } = this.state;     user[event.target.name] = event.target.value;     this.setState({ user });   };    onSubmit = () => {     const {       user: { username, password }     } = this.state;     let err = {};      if (!username) {       err.username = "Enter your username!";     }      if (password.length < 8) {       err.password = "Password must be at least 8 characters!";     }      this.setState({ errors: err }, () => {       if (Object.getOwnPropertyNames(this.state.errors).length === 0) {         this.setState({ submitted: true });       }     });   };    render() {     const {       submitted,       errors,       user: { username, password }     } = this.state;     return (       <React.Fragment>         {submitted ? (           <p>Welcome onboard, {username}!</p>         ) : (           <React.Fragment>             <h3>Login!</h3>             <FormInput               label="Username"               name="username"               type="text"               value={username}               onChange={this.handleChange}               placeholder="Enter username..."               error={errors.username}               required               className="input"             />              <FormInput               label="Password"               name="password"               type="password"               value={password}               onChange={this.handleChange}               placeholder="Enter password..."               error={errors.password}               className="input"               required             />              <Button               type="submit"               label="Submit"               className="button"               handleClick={this.onSubmit}             />           </React.Fragment>         )}       </React.Fragment>     );   } }

See how LoginPage uses the FormInput twice? We’re using it both as the text input for a username and another text input for a password. If we want to make any changes to how the input functions, we can make those changes inside the FormInput component file we created and they will be applied to every instance where the input component is used. That’s the fundamental upside to having reusable components: you don’t have to repeat yourself.

Even the errors are displayed from the FormInput component.

The onSubmit function first validates the user object which we get from the form, ensuring that it fits the structure that there is a value for username. Notice that we can even extend the input’s functionality, like we did to check that the password contains at least eight characters.

If you look at the code, you’ll see a have a Button component in there. That’s not the same as a HTML <button> element, but instead another component that takes the props that define the type of button we want (submit, reset, button), its class name, what to do on click, and the label. There are lots of other button attributes we could integrate to enforce whatever standard is needed.

const Button = (props) => (   <button     type={props.type}     className={props.className}     onClick={props.handleClick}   >     {props.label}   </button> )

Here’s our final login form when all of our components are put together.

See the Pen
Reusable Button Component
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.


Wanna give this a try yourself? Try working on a reusable <select> element. If that’s too difficult, you can start with a <textarea> element, then perhaps a checkbox before hopping over to <select>. The key idea is to make it generic. I’ll like to see what you came up with, so link up your work in the comment section!

The post Demonstrating Reusable React Components in a Form appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Thinking in React Hooks

Amelia Wattenberger has written this wonderful and interactive piece about React Hooks and details how they can clean up code and remove all those troubling lifecycle events:

React introduced hooks one year ago, and they’ve been a game-changer for a lot of developers. There are tons of how-to introduction resources out there, but I want to talk about the fundamental mindset change when switching from React class components to function components + hooks.

Make sure you check out that lovely animation when you select the code, too. It’s a pretty smart effect to show the old way of doing things versus the new and fancy way. Also make sure to check out our very own Intro to React Hooks once you’re finished to find more examples of this pattern in use.

Direct Link to ArticlePermalink

The post Thinking in React Hooks appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

A Dark Mode Toggle with React and ThemeProvider

I like when websites have a dark mode option. Dark mode makes web pages easier for me to read and helps my eyes feel more relaxed. Many websites, including YouTube and Twitter, have implemented it already, and we’re starting to see it trickle onto many other sites as well.

In this tutorial, we’re going to build a toggle that allows users to switch between light and dark modes, using a <ThemeProvider wrapper from the styled-components library. We’ll create a useDarkMode custom hook, which supports the prefers-color-scheme media query to set the mode according to the user’s OS color scheme settings.

If that sounds hard, I promise it’s not! Let’s dig in and make it happen.

See the Pen
Day/night mode switch toggle with React and ThemeProvider
by Maks Akymenko (@maximakymenko)
on CodePen.

Let’s set things up

We’ll use create-react-app to initiate a new project:

npx create-react-app my-app cd my-app yarn start

Next, open a separate terminal window and install styled-components:

yarn add styled-components

Next thing to do is create two files. The first is global.js, which will contain our base styling, and the second is theme.js, which will include variables for our dark and light themes:

// theme.js export const lightTheme = {   body: '#E2E2E2',   text: '#363537',   toggleBorder: '#FFF',   gradient: 'linear-gradient(#39598A, #79D7ED)', }  export const darkTheme = {   body: '#363537',   text: '#FAFAFA',   toggleBorder: '#6B8096',   gradient: 'linear-gradient(#091236, #1E215D)', }

Feel free to customize variables any way you want, because this code is used just for demonstration purposes.

// global.js // Source: https://github.com/maximakymenko/react-day-night-toggle-app/blob/master/src/global.js#L23-L41  import { createGlobalStyle } from 'styled-components';  export const GlobalStyles = createGlobalStyle`   *,   *::after,   *::before {     box-sizing: border-box;   }    body {     align-items: center;     background: $ {({ theme }) => theme.body};     color: $ {({ theme }) => theme.text};     display: flex;     flex-direction: column;     justify-content: center;     height: 100vh;     margin: 0;     padding: 0;     font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;     transition: all 0.25s linear;   }

Go to the App.js file. We’re going to delete everything in there and add the layout for our app. Here’s what I did:

import React from 'react'; import { ThemeProvider } from 'styled-components'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global';  function App() {   return (     <ThemeProvider theme={lightTheme}>       <>         <GlobalStyles />         <button>Toggle theme</button>         <h1>It's a light theme!</h1>         <footer>         </footer>       </>     </ThemeProvider>   ); }  export default App;

This imports our light and dark themes. The ThemeProvider component also gets imported and is passed the light theme (lightTheme) styles inside. We also import GlobalStyles to tighten everything up in one place.

Here’s roughly what we have so far:

Now, the toggling functionality

There is no magic switching between themes yet, so let’s implement toggling functionality. We are only going to need a couple lines of code to make it work.

First, import the useState hook from react:

// App.js import React, { useState } from 'react';

Next, use the hook to create a local state which will keep track of the current theme and add a function to switch between themes on click:

// App.js const [theme, setTheme] = useState('light');  // The function that toggles between themes const toggleTheme = () => {   // if the theme is not light, then set it to dark   if (theme === 'light') {     setTheme('dark');   // otherwise, it should be light   } else {     setTheme('light');   } }

After that, all that’s left is to pass this function to our button element and conditionally change the theme. Take a look:

// App.js import React, { useState } from 'react'; import { ThemeProvider } from 'styled-components'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global';  // The function that toggles between themes function App() {   const [theme, setTheme] = useState('light');   const toggleTheme = () => {     if (theme === 'light') {       setTheme('dark');     } else {       setTheme('light');     }   }      // Return the layout based on the current theme   return (     <ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>       <>         <GlobalStyles />         // Pass the toggle functionality to the button         <button onClick={toggleTheme}>Toggle theme</button>         <h1>It's a light theme!</h1>         <footer>         </footer>       </>     </ThemeProvider>   ); }  export default App;

How does it work?

// global.js background: $ {({ theme }) => theme.body}; color: $ {({ theme }) => theme.text}; transition: all 0.25s linear;

Earlier in our GlobalStyles, we assigned background and color properties to values from the theme object, so now, every time we switch the toggle, values change depending on the darkTheme and lightTheme objects that we are passing to ThemeProvider. The transition property allows us to make this change a little more smoothly than working with keyframe animations.

Now we need the toggle component

We’re generally done here because you now know how to create toggling functionality. However, we can always do better, so let’s improve the app by creating a custom Toggle component and make our switch functionality reusable. That’s one of the key benefits to making this in React, right?

We’ll keep everything inside one file for simplicity’s sake,, so let’s create a new one called Toggle.js and add the following:

// Toggle.js import React from 'react' import { func, string } from 'prop-types'; import styled from 'styled-components'; // Import a couple of SVG files we'll use in the design: https://www.flaticon.com import { ReactComponent as MoonIcon } from 'icons/moon.svg'; import { ReactComponent as SunIcon } from 'icons/sun.svg';  const Toggle = ({ theme, toggleTheme }) => {   const isLight = theme === 'light';   return (     <button onClick={toggleTheme} >       <SunIcon />       <MoonIcon />     </button>   ); };  Toggle.propTypes = {   theme: string.isRequired,   toggleTheme: func.isRequired, }  export default Toggle;

You can download icons from here and here. Also, if we want to use icons as components, remember about importing them as React components.

We passed two props inside: the theme will provide the current theme (light or dark) and toggleTheme function will be used to switch between them. Below we created an isLight variable, which will return a boolean value depending on our current theme. We’ll pass it later to our styled component.

We’ve also imported a styled function from styled-components, so let’s use it. Feel free to add this on top your file after the imports or create a dedicated file for that (e.g. Toggle.styled.js) like I have below. Again, this is purely for presentation purposes, so you can style your component as you see fit.

// Toggle.styled.js const ToggleContainer = styled.button`   background: $ {({ theme }) => theme.gradient};   border: 2px solid $ {({ theme }) => theme.toggleBorder};   border-radius: 30px;   cursor: pointer;   display: flex;   font-size: 0.5rem;   justify-content: space-between;   margin: 0 auto;   overflow: hidden;   padding: 0.5rem;   position: relative;   width: 8rem;   height: 4rem;    svg {     height: auto;     width: 2.5rem;     transition: all 0.3s linear;          // sun icon     &:first-child {       transform: $ {({ lightTheme }) => lightTheme ? 'translateY(0)' : 'translateY(100px)'};     }          // moon icon     &:nth-child(2) {       transform: $ {({ lightTheme }) => lightTheme ? 'translateY(-100px)' : 'translateY(0)'};     }   } `;

Importing icons as components allows us to directly change the styles of the SVG icons. We’re checking if the lightTheme is an active one, and if so, we move the appropriate icon out of the visible area — sort of like the moon going away when it’s daytime and vice versa.

Don’t forget to replace the button with the ToggleContainer component in Toggle.js, regardless of whether you’re styling in separate file or directly in Toggle.js. Be sure to pass the isLight variable to it to specify the current theme. I called the prop lightTheme so it would clearly reflect its purpose.

The last thing to do is import our component inside App.js and pass required props to it. Also, to add a bit more interactivity, I’ve passed condition to toggle between “light” and “dark” in the heading when the theme changes:

// App.js <Toggle theme={theme} toggleTheme={toggleTheme} /> <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>

Don’t forget to credit the flaticon.com authors for the providing the icons.

// App.js <span>Credits:</span> <small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small> <small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>

Now that’s better:

The useDarkMode hook

While building an application, we should keep in mind that the app must be scalable, meaning, reusable, so we can use in it many places, or even different projects.

That is why it would be great if we move our toggle functionality to a separate place — so, why not to create a dedicated account hook for that?

Let’s create a new file called useDarkMode.js in the project src directory and move our logic into this file with some tweaks:

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const toggleTheme = () => {     if (theme === 'light') {       window.localStorage.setItem('theme', 'dark')       setTheme('dark')     } else {       window.localStorage.setItem('theme', 'light')       setTheme('light')     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     localTheme && setTheme(localTheme);   }, []);    return [theme, toggleTheme] };

We’ve added a couple of things here. We want our theme to persist between sessions in the browser, so if someone has chosen a dark theme, that’s what they’ll get on the next visit to the app. That’s a huge UX improvement. For this reasons we use localStorage.

We’ve also implemented the useEffect hook to check on component mounting. If the user has previously selected a theme, we will pass it to our setTheme function. In the end, we will return our theme, which contains the chosen theme and toggleTheme function to switch between modes.

Now, let’s implement the useDarkMode hook. Go into App.js, import the newly created hook, destructure our theme and toggleTheme properties from the hook, and, put them where they belong:

// App.js import React from 'react'; import { ThemeProvider } from 'styled-components'; import { useDarkMode } from './useDarkMode'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global'; import Toggle from './components/Toggle';  function App() {   const [theme, toggleTheme] = useDarkMode();   const themeMode = theme === 'light' ? lightTheme : darkTheme;    return (     <ThemeProvider theme={themeMode}>       <>         <GlobalStyles />         <Toggle theme={theme} toggleTheme={toggleTheme} />         <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>         <footer>           Credits:           <small>Sun icon made by smalllikeart from www.flaticon.com</small>           <small>Moon icon made by Freepik from www.flaticon.com</small>         </footer>       </>     </ThemeProvider>   ); }  export default App;

This almost works almost perfectly, but there is one small thing we can do to make our experience better. Switch to dark theme and reload the page. Do you see that the sun icon loads before the moon icon for a brief moment?

That happens because our useState hook initiates the light theme initially. After that, useEffect runs, checks localStorage and only then sets the theme to dark.

So far, I found two solutions. The first is to check if there is a value in localStorage in our useState:

// useDarkMode.js const [theme, setTheme] = useState(window.localStorage.getItem('theme') || 'light');

However, I am not sure if it’s a good practice to do checks like that inside useState, so let me show you a second solution, that I’m using.

This one will be a bit more complicated. We will create another state and call it componentMounted. Then, inside the useEffect hook, where we check our localTheme, we’ll add an else statement, and if there is no theme in localStorage, we’ll add it. After that, we’ll set setComponentMounted to true. In the end, we add componentMounted to our return statement.

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);   const toggleTheme = () => {     if (theme === 'light') {       window.localStorage.setItem('theme', 'dark');       setTheme('dark');     } else {       window.localStorage.setItem('theme', 'light');       setTheme('light');     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     if (localTheme) {       setTheme(localTheme);     } else {       setTheme('light')       window.localStorage.setItem('theme', 'light')     }     setComponentMounted(true);   }, []);      return [theme, toggleTheme, componentMounted] };

You might have noticed that we’ve got some pieces of code that are repeated. We always try to follow the DRY principle while writing the code, and right here we’ve got a chance to use it. We can create a separate function that will set our state and pass theme to the localStorage. I believe, that the best name for it will be setTheme, but we’ve already used it, so let’s call it setMode:

// useDarkMode.js const setMode = mode => {   window.localStorage.setItem('theme', mode)   setTheme(mode) };

With this function in place, we can refactor our useDarkMode.js a little:

// useDarkMode.js import { useEffect, useState } from 'react'; export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);    const setMode = mode => {     window.localStorage.setItem('theme', mode)     setTheme(mode)   };    const toggleTheme = () => {     if (theme === 'light') {       setMode('dark');     } else {       setMode('light');     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     if (localTheme) {       setTheme(localTheme);     } else {       setMode('light');     }     setComponentMounted(true);   }, []);    return [theme, toggleTheme, componentMounted] };

We’ve only changed code a little, but it looks so much better and is easier to read and understand!

Did the component mount?

Getting back to componentMounted property. We will use it to check if our component has mounted because this is what happens in useEffect hook.

If it hasn’t happened yet, we will render an empty div:

// App.js if (!componentMounted) {   return <div /> };

Here is how complete code for the App.js:

// App.js import React from 'react'; import { ThemeProvider } from 'styled-components'; import { useDarkMode } from './useDarkMode'; import { lightTheme, darkTheme } from './theme'; import { GlobalStyles } from './global'; import Toggle from './components/Toggle';  function App() {   const [theme, toggleTheme, componentMounted] = useDarkMode();    const themeMode = theme === 'light' ? lightTheme : darkTheme;    if (!componentMounted) {     return <div />   };    return (     <ThemeProvider theme={themeMode}>       <>         <GlobalStyles />         <Toggle theme={theme} toggleTheme={toggleTheme} />         <h1>It's a {theme === 'light' ? 'light theme' : 'dark theme'}!</h1>         <footer>           <span>Credits:</span>           <small><b>Sun</b> icon made by <a href="https://www.flaticon.com/authors/smalllikeart">smalllikeart</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>           <small><b>Moon</b> icon made by <a href="https://www.freepik.com/home">Freepik</a> from <a href="https://www.flaticon.com">www.flaticon.com</a></small>         </footer>       </>     </ThemeProvider>   ); }  export default App;

Using the user’s preferred color scheme

This part is not required, but it will let you achieve even better user experience. This media feature is used to detect if the user has requested the page to use a light or dark color theme based on the settings in their OS. For example, if a user’s default color scheme on a phone or laptop is set to dark, your website will change its color scheme accordingly to it. It’s worth noting that this media query is still a work in progress and is included in the Media Queries Level 5 specification, which is in Editor’s Draft.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
76 62 67 No 76 12.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
13 No No 76 No 68

The implementation is pretty straightforward. Because we’re working with a media query, we need to check if the browser supports it in the useEffect hook and set appropriate theme. To do that, we’ll use window.matchMedia to check if it exists and whether dark mode is supported. We also need to remember about the localTheme because, if it’s available, we don’t want to overwrite it with the dark value unless, of course, the value is set to light.

If all checks are passed, we will set the dark theme.

// useDarkMode.js useEffect(() => { if (   window.matchMedia &&   window.matchMedia('(prefers-color-scheme: dark)').matches &&    !localTheme ) {   setTheme('dark')   } })

As mentioned before, we need to remember about the existence of localTheme — that’s why we need to implement our previous logic where we’ve checked for it.

Here’s what we had from before:

// useDarkMode.js useEffect(() => { const localTheme = window.localStorage.getItem('theme');   if (localTheme) {     setTheme(localTheme);   } else {     setMode('light');   } })

Let’s mix it up. I’ve replaced the if and else statements with ternary operators to make things a little more readable as well:

// useDarkMode.js useEffect(() => { const localTheme = window.localStorage.getItem('theme'); window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?   setMode('dark') :   localTheme ?     setTheme(localTheme) :     setMode('light');}) })

Here’s the userDarkMode.js file with the complete code:

// useDarkMode.js import { useEffect, useState } from 'react';  export const useDarkMode = () => {   const [theme, setTheme] = useState('light');   const [componentMounted, setComponentMounted] = useState(false);   const setMode = mode => {     window.localStorage.setItem('theme', mode)     setTheme(mode)   };    const toggleTheme = () => {     if (theme === 'light') {       setMode('dark')     } else {       setMode('light')     }   };    useEffect(() => {     const localTheme = window.localStorage.getItem('theme');     window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches && !localTheme ?       setMode('dark') :       localTheme ?         setTheme(localTheme) :         setMode('light');     setComponentMounted(true);   }, []);    return [theme, toggleTheme, componentMounted] };

Give it a try! It changes the mode, persists the theme in localStorage, and also sets the default theme accordingly to the OS color scheme if it’s available.


Congratulations, my friend! Great job! If you have any questions about implementation, feel free to send me a message!

The post A Dark Mode Toggle with React and ThemeProvider appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]

Filtering Data Client-Side: Comparing CSS, jQuery, and React

Say you have a list of 100 names:

<ul>   <li>Randy Hilpert</li>   <li>Peggie Jacobi</li>   <li>Ethelyn Nolan Sr.</li>    <!-- and then some --> </ul>

…or file names, or phone numbers, or whatever. And you want to filter them client-side, meaning you aren’t making a server-side request to search through data and return results. You just want to type “rand” and have it filter the list to include “Randy Hilpert” and “Danika Randall” because they both have that string of characters in them. Everything else isn’t included in the results.

Let’s look at how we might do that with different technologies.

CSS can sorta do it, with a little help.

CSS can’t select things based on the content they contain, but it can select on attributes and the values of those attributes. So let’s move the names into attributes as well.

<ul>   <li data-name="Randy Hilpert">Randy Hilpert</li>   <li data-name="Peggie Jacobi">Peggie Jacobi</li>   <li data-name="Ethelyn Nolan Sr.">Ethelyn Nolan Sr.</li>    ... </ul>

Now to filter that list for names that contain “rand”, it’s very easy:

li {   display: none; } li[data-name*="rand" i] {   display: list-item; }

Note the i on Line 4. That means “case insensitive” which is very useful here.

To make this work dynamically with a filter <input>, we’ll need to get JavaScript involved to not only react to the filter being typed in, but generate CSS that matches what is being searched.

Say we have a <style> block sitting on the page:

<style id="cssFilter">   /* dynamically generated CSS will be put in here */ </style>

We can watch for changes on our filter input and generate that CSS:

filterElement.addEventListener("input", e => {   let filter = e.target.value;   let css = filter ? `     li {       display: none;     }     li[data-name*="$  {filter}" i] {       display: list-item;     }   ` : ``;   window.cssFilter.innerHTML = css; });

Note that we’re emptying out the style block when the filter is empty, so all results show.

See the Pen
Filtering Technique: CSS
by Chris Coyier (@chriscoyier)
on CodePen.

I’ll admit it’s a smidge weird to leverage CSS for this, but Tim Carry once took it way further if you’re interested in the concept.

jQuery makes it even easier.

Since we need JavaScript anyway, perhaps jQuery is an acceptable tool. There are two notable changes here:

  • jQuery can select items based on the content they contain. It has a selector API just for this. We don’t need the extra attribute anymore.
  • This keeps all the filtering to a single technology.

We still watch the input for typing, then if we have a filter term, we hide all the list items and reveal the ones that contain our filter term. Otherwise, we reveal them all again:

const listItems = $  ("li");  $  ("#filter").on("input", function() {   let filter = $  (this).val();   if (filter) {     listItems.hide();     $  (`li:contains('$  {filter}')`).show();   } else {     listItems.show();   } });

It’s takes more fiddling to make the filter case-insensitive than CSS does, but we can do it by overriding the default method:

jQuery.expr[':'].contains = function(a, i, m) {   return jQuery(a).text().toUpperCase()       .indexOf(m[3].toUpperCase()) >= 0; };

See the Pen
Filtering Technique: jQuery
by Chris Coyier (@chriscoyier)
on CodePen.

React can do it with state and rendering only what it needs.

There is no one-true-way to do this in React, but I would think it’s React-y to keep the list of names as data (like an Array), map over them, and only render what you need. Changes in the input filter the data itself and React re-renders as necessary.

If we have an names = [array, of, names], we can filter it pretty easily:

filteredNames = names.filter(name => {   return name.includes(filter); });

This time, case sensitivity can be done like this:

filteredNames = names.filter(name => {   return name.toUpperCase().includes(filter.toUpperCase()); });

Then we’d do the typical .map() thing in JSX to loop over our array and output the names.

See the Pen
Filtering Technique: React
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t have any particular preference

This isn’t the kind of thing you choose a technology for. You do it in whatever technology you already have. I also don’t think any one approach is particularly heavier than the rest in terms of technical debt.

The post Filtering Data Client-Side: Comparing CSS, jQuery, and React appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]

Using Immer for React State Management

We make use of state to keep track of application data. States change as users interact with an application. When this happens, we need to update the state that is displayed to the user, and we do this using React’s setState.

Since states are not meant to be updated directly (because React’s state has to be immutable), things can get really complicated as states become more complex. They become difficult to understand and follow.

This is where Immer comes in and that’s what we’re going to look at in this post. Using Immer, states can be simplified and much easier to follow. Immer makes use of something called “draft” which you can think of as the copy of your state, but not the state itself. It’s as though Immer hit CMD+C on the state and then cmd+V’d it somewhere else where it can be safely viewed without disturbing the original copy. Any updates you need to make happen on the draft, and the parts of the current state that change on the draft is updated.

Let’s say your application’s state looks like this;

this.state = {   name: 'Kunle',   age: 30,   city: 'Lagos,   country: 'Nigeria' }

This user happens to be celebrating his 31st birthday and which means we need to update the age value. With Immer running behind the scenes, a replica of this state will be made.

Now imagine the replica is made and handed over to a messenger, who gives the newly copied version of the state to Kunle. It means there are now two copies available — the current state and the draft copy that was handed over. Kunle then changes the age on the draft to 31. The messenger then returns to the application with the draft, compares both versions, and only updates the age since that’s the only part of the draft that changed.

It does not break the idea of an immutable state, as the current state does not get updated directly. Immer basically makes it convenient to work with immutable state.

Let’s look at an example of this at work

Say you want to build a traffic light for your community, you can give it a shot using Immer for your state updates.

See the Pen
Traffic Light Example with Reactjs
by CarterTsai (@CarterTsai)
on CodePen.

Using Immer, the component will look like this:

const {produce} = immer  class App extends React.Component {   state = {     red: 'red',      yellow: 'black',      green: 'black',     next: "yellow"   }    componentDidMount() {     this.interval = setInterval(() => this.changeHandle(), 3000);   }      componentWillUnmount()  {     clearInterval(this.interval);   }    handleRedLight = () => {     this.setState(       produce(draft => {         draft.red = 'red';         draft.yellow = 'black';         draft.green = 'black';         draft.next = 'yellow'       })     )   }      handleYellowLight = () => {     this.setState(       produce(draft => {         draft.red = 'black';         draft.yellow = 'yellow';         draft.green = 'black';         draft.next = 'green'       })     )   }      handleGreenLight = () => {     this.setState(       produce(draft => {         draft.red = 'black';         draft.yellow = 'black';         draft.green = 'green';         draft.next = 'red'       })     )   }    changeHandle = () => {     if (this.state.next === 'yellow') {       this.handleYellowLight()     } else if (this.state.next === 'green') {       this.handleGreenLight()     } else {       this.handleRedLight()     }        }    render() {     return (       <div className="box">         <div className="circle" style={{backgroundColor: this.state.red}}></div>         <div className="circle" style={{backgroundColor: this.state.yellow}}></div>         <div className="circle" style={{backgroundColor: this.state.green}}></div>       </div>   ); } };

produce is the default function we get from Immer. Here, we pass it as a value to the setState() method. The produce function takes a function which accepts draft as an argument. It is inside this function that we can then set the draft copy with which we want to update our state.

If that looks complicated, there is another way to write this. First, we create a function.

const handleLight = (state) => {   return produce(state, (draft) => {     draft.red = 'black';     draft.yellow = 'black';     draft.green = 'green';     draft.next = 'red'   }); }

We are passing the current state of the application, and the function which accepts draft as arguments to the produce function. To make use of this inside our component, we do this;

handleGreenLight = () => {   const nextState = handleLight(this.state)   this.setState(nextState) }

Another example: A shopping list

If you have been working with React for a while now, then you’re not a stranger to the spread operator. With Immer, you need not make use of the spread operator, especially when working with an array in your state.

Let’s explore that a little further by creating a shopping list application.

See the Pen
immer 2 – shopping list
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Here’s the component we’re working with:

class App extends React.Component {   constructor(props) {       super(props)              this.state = {         item: "",         price: 0,         list: [           { id: 1, name: "Cereals", price: 12 },           { id: 2, name: "Rice", price: 10 }         ]       }     }      handleInputChange = e => {       this.setState(       produce(draft => {         draft[event.target.name] = event.target.value       }))     }      handleSubmit = (e) => {       e.preventDefault()       const newItem = {         id: uuid.v4(),         name: this.state.name,         price: this.state.price       }       this.setState(         produce(draft => {           draft.list = draft.list.concat(newItem)         })       )     };    render() {     return (       <React.Fragment>         <section className="section">           <div className="box">             <form onSubmit={this.handleSubmit}>               <h2>Create your shopping list</h2>               <div>                 <input                   type="text"                   placeholder="Item's Name"                   onChange={this.handleInputChange}                   name="name"                   className="input"                   />               </div>               <div>                 <input                   type="number"                   placeholder="Item's Price"                   onChange={this.handleInputChange}                   name="price"                   className="input"                   />               </div>               <button className="button is-grey">Submit</button>             </form>           </div>                      <div className="box">             {               this.state.list.length ? (                 this.state.list.map(item => (                   <ul>                     <li key={item.id}>                       <p>{item.name}</p>                       <p>$  {item.price}</p>                     </li>                     <hr />                   </ul>                 ))               ) : <p>Your list is empty</p>             }           </div>         </section>       </React.Fragment>     )   } }  ReactDOM.render(   <App />,   document.getElementById('root') );

As items are added to the list, we need to update the state of the list to reflect those new items. To update the state of list using setState(), we’ll have to do this:

handleSubmit = (e) => {   e.preventDefault()   const newItem = {     id: uuid.v4(),     name: this.state.name,     price: this.state.price   }   this.setState({ list: [...this.state.list, newItem] }) };

If you have to update multiple states in the application, you’ll have to do a ton of spreading to create a new state using the old state and the additional value. Which can look more complex as the number of changes increases. With Immer, it becomes very easy to do that, as we did in the example above.

What if we want to add a function that gets called as a callback after the state update?In this case, let’s say we are keeping a tally of the number of items in the list and the total price of all the items.

See the Pen
immer 3 – shopping list
by Kingsley Silas Chijioke (@kinsomicrote)
on CodePen.

Say we want to calculate the amount that will be spent based on the price of items in the list, we can have the handleSubmit function look like this:

handleSubmit = (e) => {   e.preventDefault()   const newItem = {     id: uuid.v4(),     name: this.state.name,     price: this.state.price   }      this.setState(     produce(draft => {       draft.list = draft.list.concat(newItem)     }), () => {       this.calculateAmount(this.state.list)     }   ) };

First, we create an object using the data entered by the user, which we then assign to newItem. To update our application’s state, we make use of .concat() which will return a new array that’s comprised of the previous items and the new item. This updated copy is now set as the value of draft.list, which can then be used by Immer to update the state of the application.

The callback function gets called after the state update. It’s important to note that it makes use of the updated state.

The function we want to call will look like this:

calculateAmount = (list) => {   let total = 0;     for (let i = 0; i < list.length; i++) {       total += parseInt(list[i].price, 10)     }   this.setState(     produce(draft => {       draft.totalAmount = total     })   ) }

Let’s look at Immer hooks

use-immer is a hook that allows you to manage state in your React application. Let’s see this in action using a classic counter example.

import React from "react"; import {useImmer} from "use-immer";  const Counter = () => {   const [count, updateCounter] = useImmer({     value: 0   });    function increment() {     updateCounter(draft => {       draft.value = draft.value +1;     });   }    return (     <div>       <h1>         Counter {count.value}       </h1>       <br />       <button onClick={increment}>Increment</button>     </div>   ); }  export default Counter;

useImmer is similar to useState. The function returns the state and an updater function. When the component loads at first, the value of the state (which is count in this example), is the same as the value passed to useImmer. Using the updater function which is returned, we can then create an increment function to increase the value of the count.

There is also a useReducer-like hook for Immer.

import React, { useRef } from "react"; import {useImmerReducer } from "use-immer"; import uuidv4 from "uuid/v4" const initialState = []; const reducer = (draft, action) => {   switch (action.type) {     case "ADD_ITEM":       draft.push(action.item);       return;     case "CLEAR_LIST":       return initialState;     default:       return draft;   } } const Todo = () => {   const inputEl = useRef(null);   const [state, dispatch] = useImmerReducer(reducer, initialState);      const handleSubmit = (e) => {     e.preventDefault()     const newItem = {       id: uuidv4(),       text: inputEl.current.value     };     dispatch({ type: "ADD_ITEM", item: newItem });     inputEl.current.value = "";     inputEl.current.focus();   }      const handleClear = () => {     dispatch({ type: 'CLEAR_LIST' })   }      return (     <div className='App'>       <header className='App-header'>         <ul>           {state.map(todo => {             return <li key={todo.id}>{todo.text}</li>;           })}         </ul>         <form onSubmit={handleSubmit}>           <input type='text' ref={inputEl} />           <button             type='submit'           >             Add Todo           </button>         </form>         <button           onClick={handleClear}         >           Clear Todos         </button>       </header>     </div>   ); } export default Todo;

useImmerReducer takes in a reducer function and the initial state, and it returns both state and the dispatch function. We can then loop through the state to display the items we have. We dispatch an action when submitting a todo item and clearing the list of them. The dispatched action has a type which we use in determining what to do in the reducer function.
In the reducer function, we make use of draft like we did before, instead of state. With that, we have a convenient way of manipulating the state of our application.

You can find the code used in the above example on GitHub.

That’s a look at Immer!

Going forward, you can begin to make use of Immer in your next project, or even slowly begin to use it in the current project you’re working on. It has proven to aid in making state management convenient.

The post Using Immer for React State Management appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]