Tag: React

Accessing Your Data With Netlify Functions and React

(This is a sponsored post.)

Static site generators are popular for their speed, security, and user experience. However, sometimes your application needs data that is not available when the site is built. React is a library for building user interfaces that helps you retrieve and store dynamic data in your client application. 

Fauna is a flexible, serverless database delivered as an API that completely eliminates operational overhead such as capacity planning, data replication, and scheduled maintenance. Fauna allows you to model your data as documents, making it a natural fit for web applications written with React. Although you can access Fauna directly via a JavaScript driver, this requires a custom implementation for each client that connects to your database. By placing your Fauna database behind an API, you can enable any authorized client to connect, regardless of the programming language.

Netlify Functions allow you to build scalable, dynamic applications by deploying server-side code that works as API endpoints. In this tutorial, you build a serverless application using React, Netlify Functions, and Fauna. You learn the basics of storing and retrieving your data with Fauna. You create and deploy Netlify Functions to access your data in Fauna securely. Finally, you deploy your React application to Netlify.

Getting started with Fauna

Fauna is a distributed, strongly consistent OLTP NoSQL serverless database that is ACID-compliant and offers a multi-model interface. Fauna also supports document, relational, graph, and temporal data sets from a single query. First, we will start by creating a database in the Fauna console by selecting the Database tab and clicking on the Create Database button.

Next, you will need to create a Collection. For this, you will need to select a database, and under the Collections tab, click on Create Collection.

Fauna uses a particular structure when it comes to persisting data. The design consists of attributes like the example below.

{   "ref": Ref(Collection("avengers"), "299221087899615749"),   "ts": 1623215668240000,   "data": {     "id": "db7bd11d-29c5-4877-b30d-dfc4dfb2b90e",     "name": "Captain America",     "power": "High Strength",     "description": "Shield"   } }

Notice that Fauna keeps a ref column which is a unique identifier used to identify a particular document. The ts attribute is a timestamp to determine the time of creating the record and the data attribute responsible for the data.

Why creating an index is important

Next, let’s create two indexes for our avengers collection. This will be pretty valuable in the latter part of the project. You can create an index from the Index tab or from the Shell tab, which provides a console to execute scripts. Fauna supports two types of querying techniques: FQL (Fauna’s Query language) and GraphQL. FQL operates based on the schema of Fauna, which includes documents, collections, indexes, sets, and databases. 

Let’s create the indexes from the shell.

This command will create an index on the Collection, which will create an index by the id field inside the data object. This index will return a ref of the data object. Next, let’s create another index for the name attribute and name it avenger_by_name.

Creating a server key

To create a server key, we need to navigate the Security tab and click on the New Key button. This section will prompt you to create a key for a selected database and the user’s role.

Getting started with Netlify functions and React

In this section, we’ll see how we create Netlify functions with React. We will be using create-react-app to create the react app.

npx create-react-app avengers-faunadb

After creating the react app, let’s install some dependencies, including Fauna and Netlify dependencies.

yarn add axios bootstrap node-sass uuid faunadb react-netlify-identity react-netlify-identity-widget

Now let’s create our first Netlfiy function. To make the functions, first, we need to install Netlifiy CLI globally.

npm install netlify-cli -g

Now that the CLI is installed, let’s create a .env file on our project root with the following fields.

FAUNADB_SERVER_SECRET= <FaunaDB secret key> REACT_APP_NETLIFY= <Netlify app url>

Next, Let’s see how we can start with creating netlify functions. For this, we will need to create a directory in our project root called functions and a file called netlify.toml, which will be responsible for maintaining configurations for our Netlify project. This file defines our function’s directory, build directory, and commands to execute.

[build] command = "npm run build" functions = "functions/" publish = "build"  [[redirects]]   from = "/api/*"   to = "/.netlify/functions/:splat"   status = 200   force = true

We will do some additional configuration for the Netlify configuration file, like in the redirection section in this example. Notice that we are changing the default path of the Netlify function of /.netlify/** to /api/. This configuration is mainly for the improvement of the look and field of the API URL. So to trigger or call our function, we can use the path:

https://domain.com/api/getPokemons

 …instead of:

https://domain.com/.netlify/getPokemons

Next, let’s create our Netlify function in the functions directory. But, first, let’s make a connection file for Fauna called util/connections.js, returning a Fauna connection object.

const faunadb = require('faunadb'); const q = faunadb.query  const clientQuery = new faunadb.Client({   secret: process.env.FAUNADB_SERVER_SECRET, });  module.exports = { clientQuery, q };

Next, let’s create a helper function checking for reference and returning since we will need to parse the data on several occasions throughout the application. This file will be util/helper.js.

const responseObj = (statusCode, data) => {   return {     statusCode: statusCode,     headers: {      /* Required for CORS support to work */       "Access-Control-Allow-Origin": "*",       "Access-Control-Allow-Headers": "*",       "Access-Control-Allow-Methods": "GET, POST, OPTIONS",     },    body: JSON.stringify(data)   }; };  const requestObj = (data) => {   return JSON.parse(data); }  module.exports = { responseObj: responseObj, requestObj: requestObj }

Notice that the above helper functions handle the CORS issues, stringifying and parsing of JSON data. Let’s create our first function, getAvengers, which will return all the data.

const { responseObj } = require('./util/helper'); const { q, clientQuery } = require('./util/connection');  exports.handler = async (event, context) => {   try {    let avengers = await clientQuery.query(      q.Map(        q.Paginate(q.Documents(q.Collection('avengers'))),        q.Lambda(x => q.Get(x))       )     )     return responseObj(200, avengers)   } catch (error) {     console.log(error)     return responseObj(500, error);   } };

In the above code example, you can see that we have used several FQL commands like Map, Paginate, Lamda. The Map key is used to iterate through the array, and it takes two arguments: an Array and Lambda. We have passed the Paginate for the first parameter, which will check for reference and return a page of results (an array). Next, we used a Lamda statement, an anonymous function that is quite similar to an anonymous arrow function in ES6.

Next, Let’s create our function AddAvenger responsible for creating/inserting data into the Collection.

const { requestObj, responseObj } = require('./util/helper'); const { q, clientQuery } = require('./util/connection');  exports.handler = async (event, context) => {   let data = requestObj(event.body);    try {     let avenger = await clientQuery.query(       q.Create(         q.Collection('avengers'),         {           data: {             id: data.id,             name: data.name,             power: data.power,             description: data.description           }         }       )     );      return responseObj(200, avenger)   } catch (error) {     console.log(error)     return responseObj(500, error);   }   };

To save data for a particular collection, we will have to pass, or data to the data:{} object like in the above code example. Then we need to pass it to the Create function and point it to the collection you want and the data. So, let’s run our code and see how it works through the netlify dev command.

Let’s trigger the GetAvengers function through the browser through the URL http://localhost:8888/api/GetAvengers.

The above function will fetch the avenger object by the name property searching from the avenger_by_name index. But, first, let’s invoke the GetAvengerByName function through a Netlify function. For that, let’s create a function called SearchAvenger.

const { responseObj } = require('./util/helper'); const { q, clientQuery } = require('./util/connection');  exports.handler = async (event, context) => {   const {     queryStringParameters: { name },   } = event;    try {     let avenger = await clientQuery.query(       q.Call(q.Function("GetAvengerByName"), [name])     );     return responseObj(200, avenger)   } catch (error) {     console.log(error)     return responseObj(500, error);   } };

Notice that the Call function takes two arguments where the first parameter will be the reference for the FQL function that we created and the data that we need to pass to the function.

Calling the Netlify function through React

Now that several functions are available let’s consume those functions through React. Since the functions are REST APIs, let’s consume them via Axios, and for state management, let’s use React’s Context API. Let’s start with the Application context called AppContext.js.

import { createContext, useReducer } from "react"; import AppReducer from "./AppReducer"  const initialState = {     isEditing: false,     avenger: { name: '', description: '', power: '' },     avengers: [],     user: null,     isLoggedIn: false };  export const AppContext = createContext(initialState);  export const AppContextProvider = ({ children }) => {     const [state, dispatch] = useReducer(AppReducer, initialState);      const login = (data) => { dispatch({ type: 'LOGIN', payload: data }) }     const logout = (data) => { dispatch({ type: 'LOGOUT', payload: data }) }     const getAvenger = (data) => { dispatch({ type: 'GET_AVENGER', payload: data }) }     const updateAvenger = (data) => { dispatch({ type: 'UPDATE_AVENGER', payload: data }) }     const clearAvenger = (data) => { dispatch({ type: 'CLEAR_AVENGER', payload: data }) }     const selectAvenger = (data) => { dispatch({ type: 'SELECT_AVENGER', payload: data }) }     const getAvengers = (data) => { dispatch({ type: 'GET_AVENGERS', payload: data }) }     const createAvenger = (data) => { dispatch({ type: 'CREATE_AVENGER', payload: data }) }     const deleteAvengers = (data) => { dispatch({ type: 'DELETE_AVENGER', payload: data }) }      return <AppContext.Provider value={{         ...state,         login,         logout,         selectAvenger,         updateAvenger,         clearAvenger,         getAvenger,         getAvengers,         createAvenger,         deleteAvengers     }}>{children}</AppContext.Provider> }  export default AppContextProvider;

Let’s create the Reducers for this context in the AppReducer.js file, Which will consist of a reducer function for each operation in the application context.

const updateItem = (avengers, data) => {     let avenger = avengers.find((avenger) => avenger.id === data.id);     let updatedAvenger = { ...avenger, ...data };     let avengerIndex = avengers.findIndex((avenger) => avenger.id === data.id);     return [         ...avengers.slice(0, avengerIndex),         updatedAvenger,         ...avengers.slice(++avengerIndex),     ]; }  const deleteItem = (avengers, id) => {     return avengers.filter((avenger) => avenger.data.id !== id) }  const AppReducer = (state, action) => {     switch (action.type) {         case 'SELECT_AVENGER':             return {                 ...state,                 isEditing: true,                 avenger: action.payload             }         case 'CLEAR_AVENGER':             return {                 ...state,                 isEditing: false,                 avenger: { name: '', description: '', power: '' }             }         case 'UPDATE_AVENGER':             return {                 ...state,                 isEditing: false,                 avengers: updateItem(state.avengers, action.payload)             }         case 'GET_AVENGER':             return {                 ...state,                 avenger: action.payload.data             }         case 'GET_AVENGERS':             return {                 ...state,                 avengers: Array.isArray(action.payload && action.payload.data) ? action.payload.data : [{ ...action.payload }]             };         case 'CREATE_AVENGER':             return {                 ...state,                 avengers: [{ data: action.payload }, ...state.avengers]             };         case 'DELETE_AVENGER':             return {                 ...state,                 avengers: deleteItem(state.avengers, action.payload)             };         case 'LOGIN':             return {                 ...state,                 user: action.payload,                 isLoggedIn: true             };         case 'LOGOUT':             return {                 ...state,                 user: null,                 isLoggedIn: false             };         default:             return state     } }  export default AppReducer; 

Since the application context is now available, we can fetch data from the Netlify functions that we have created and persist them in our application context. So let’s see how to call one of these functions.

const { avengers, getAvengers } = useContext(AppContext);  const GetAvengers = async () => {   let { data } = await axios.get('/api/GetAvengers);   getAvengers(data) }

To get the data to the application contexts, let’s import the function getAvengers from our application context and pass the data fetched by the get call. This function will call the reducer function, which will keep the data in the context. To access the context, we can use the attribute called avengers. Next, let’s see how we could save data on the avengers collection.

const { createAvenger } = useContext(AppContext);  const CreateAvenger = async (e) => {   e.preventDefault();   let new_avenger = { id: uuid(), ...newAvenger }   await axios.post('/api/AddAvenger', new_avenger);   clear();   createAvenger(new_avenger) }

The above newAvenger object is the state object which will keep the form data. Notice that we pass a new id of type uuid to each of our documents. Thus, when the data is saved in Fauna, We will be using the createAvenger function in the application context to save the data in our context. Similarly, we can invoke all the netlify functions with CRUD operations like this via Axios.

How to deploy the application to Netlify

Now that we have a working application, we can deploy this app to Netlify. There are several ways that we can deploy this application:

  1. Connecting and deploying the application through GitHub
  2. Deploying the application through the Netlify CLI

Using the CLI will prompt you to enter specific details and selections, and the CLI will handle the rest. But in this example, we will be deploying the application through Github. So first, let’s log in to the Netlify dashboard and click on New Site from Git button. Next, It will prompt you to select the Repo you need to deploy and the configurations for your site like build command, build folder, etc.

How to authenticate and authorize functions by Netlify Identity

Netlify Identity provides a full suite of authentication functionality to your application which will help us to manage authenticated users throughout the application. Netlify Identity can be integrated easily into the application without using any other 3rd party service and libraries. To enable Netlify Identity, we need to login into our Neltify dashboard, and under our deployed site, we need to move to the Identity tab and allow the identity feature.

Enabling Identity will provide a link to your netlify identity. You will have to copy that URL and add it to the .env file of your application for REACT_APP_NETLIFY. Next, We need to add the Netlify Identity to our React application through the netlify-identity-widget and the Netlify functions. But, first, let’s add the REACT_APP_NETLIFY property for the Identity Context Provider component in the index.js file.

import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import "react-netlify-identity-widget/styles.css" import 'bootstrap/dist/css/bootstrap.css'; import App from './App'; import { IdentityContextProvider } from "react-netlify-identity-widget" const url = process.env.REACT_APP_NETLIFY;  ReactDOM.render(   <IdentityContextProvider url=https://css-tricks.com/accessing-data-netlify-functions-react/>     <App />   </IdentityContextProvider>,   document.getElementById('root') );

This component is the Navigation bar that we use in this application. This component will be on top of all the other components to be the ideal place to handle the authentication. This react-netlify-identity-widget will add another component that will handle the user signI= in and sign up.

Next, let’s use the Identity in our Netlify functions. Identity will introduce some minor modifications to our functions, like the below function GetAvenger.

const { responseObj } = require('./util/helper'); const { q, clientQuery } = require('./util/connection');  exports.handler = async (event, context) => {     if (context.clientContext.user) {         const {             queryStringParameters: { id },         } = event;         try {             const avenger = await clientQuery.query(                 q.Get(                     q.Match(q.Index('avenger_by_id'), id)                 )             );             return responseObj(200, avenger)         } catch (error) {             console.log(error)             return responseObj(500, error);         }     } else {         return responseObj(401, 'Unauthorized');     } };

The context of each request will consist of a property called clientContext, which will consist of authenticated user details. In the above example, we use a simple if condition to check the user context. 

To get the clientContext in each of our requests, we need to pass the user token through the Authorization Headers. 

const { user } = useIdentityContext();  const GetAvenger = async (id) => {   let { data } = await axios.get('/api/GetAvenger/?id=' + id, user && {     headers: {       Authorization: `Bearer $ {user.token.access_token}`     }   });   getAvenger(data) }

This user token will be available in the user context once logged in to the application through the netlify identity widget.

As you can see, Netlify functions and Fauna look to be a promising duo for building serverless applications. You can follow this GitHub repo for the complete code and refer to this URL for the working demo.

Conclusion

In conclusion, Fauna and Netlify look to be a promising duo for building serverless applications. Netlify also provides the flexibility to extend its functionality through the plugins to enhance the experience. The pricing plan with pay as you go is ideal for developers to get started with fauna. Fauna is extremely fast, and it auto-scales so that developers will have the time to focus on their development more than ever. Fauna can handle complex database operations where you would find in Relational, Document, Graph, Temporal databases. Fauna Driver support all the major languages such as Android, C#, Go, Java, JavaScript, Python, Ruby, Scala, and Swift. With all these excellent features, Fauna looks to be one of the best Serverless databases. For more information, go through Fauna documentation.


The post Accessing Your Data With Netlify Functions and React appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,

Three Buggy React Code Examples and How to Fix Them

There’s usually more than one way to code a thing in React. And while it’s possible to create the same thing different ways, there may be one or two approaches that technically work “better” than others. I actually run into plenty of examples where the code used to build a React component is technically “correct” but opens up issues that are totally avoidable.

So, let’s look at some of those examples. I’m going to provide three instances of “buggy” React code that technically gets the job done for a particular situation, and ways it can be improved to be more maintainable, resilient, and ultimately functional.

This article assumes some knowledge of React hooks. It isn’t an introduction to hooks—you can find a good introduction from Kingsley Silas on CSS Tricks, or take a look at the React docs to get acquainted with them. We also won’t be looking at any of that exciting new stuff coming up in React 18. Instead, we’re going to look at some subtle problems that won’t completely break your application, but might creep into your codebase and can cause strange or unexpected behavior if you’re not careful.

Buggy code #1: Mutating state and props

It’s a big anti-pattern to mutate state or props in React. Don’t do this!

This is not a revolutionary piece of advice—it’s usually one of the first things you learn if you’re getting started with React. But you might think you can get away with it (because it seems like you can in some cases).

I’m going to show you how bugs might creep into your code if you’re mutating props. Sometimes you’ll want a component that will show a transformed version of some data. Let’s create a parent component that holds a count in state and a button that will increment it. We’ll also make a child component that receives the count via props and shows what the count would look like with 5 added to it.

Here’s a Pen that demonstrates a naïve approach:

This example works. It does what we want it to do: we click the increment button and it adds one to the count. Then the child component is re-rendered to show what the count would look like with 5 added on. We changed the props in the child here and it works fine! Why has everybody been telling us mutating props is so bad?

Well, what if later we refactor the code and need to hold the count in an object? This might happen if we need to store more properties in the same useState hook as our codebase grows larger.

Instead of incrementing the number held in state, we increment the count property of an object held in state. In our child component, we receive the object through props and add to the count property to show what the count would look like if we added 5.

Let’s see how this goes. Try incrementing the state a few times in this pen:

Oh no! Now when we increment the count it seems to add 6 on every click! Why is this happening? The only thing that changed between these two examples is that we used an object instead of a number!

More experienced JavaScript programmers will know that the big difference here is that primitive types such as numbers, booleans and strings are immutable and passed by value, whereas objects are passed by reference.

This means that:

  • If you put a number in a variable, assign another variable to it, then change the second variable, the first variable will not be changed.
  • If you if you put an object in a variable, assign another variable to it, then change the second variable, the first variable will get changed.

When the child component changes a property of the state object, it’s adding 5 to the same object React uses when updating the state. This means that when our increment function fires after a click, React uses the same object after it has been manipulated by our child component, which shows as adding 6 on every click.

The solution

There are multiple ways to avoid these problems. For a situation as simple as this, you could avoid any mutation and express the change in a render function:

function Child({state}){   return <div><p>count + 5 = {state.count + 5} </p></div> }

However, in a more complicated case, you might need to reuse state.count + 5 multiple times or pass the transformed data to multiple children.

One way to do this is to create a copy of the prop in the child, then transform the properties on the cloned data. There’s a couple of different ways to clone objects in JavaScript with various tradeoffs. You can use object literal and spread syntax:

function Child({state}){ const copy = {...state};   return <div><p>count + 5 = {copy.count + 5} </p></div> }

But if there are nested objects, they will still reference the old version. Instead, you could convert the object to JSON then immediately parse it:

JSON.parse(JSON.stringify(myobject)) 

This will work for most simple object types. But if your data uses more exotic types, you might want to use a library. A popular method would be to use lodash’s deepClone. Here’s a Pen that shows a fixed version using object literal and spread syntax to clone the object:

One more option is to use a library like Immutable.js. If you have a rule to only use immutable data structures, you’ll be able to trust that your data won’t get unexpectedly mutated. Here’s one more example using the immutable Map class to represent the state of the counter app:

Buggy code #2: Derived state

Let’s say we have a parent and a child component. They both have useState hooks holding a count. And let’s say the parent passes its state down as prop down to the child, which the child uses to initialize its count.

function Parent(){   const [parentCount,setParentCount] = useState(0);   return <div>     <p>Parent count: {parentCount}</p>     <button onClick={()=>setParentCount(c=>c+1)}>Increment Parent</button>     <Child parentCount={parentCount}/>   </div>; }  function Child({parentCount}){  const [childCount,setChildCount] = useState(parentCount);   return <div>     <p>Child count: {childCount}</p>     <button onClick={()=>setChildCount(c=>c+1)}>Increment Child</button>   </div>; }

What happens to the child’s state when the parent’s state changes, and the child is re-rendered with different props? Will the child state remain the same or will it change to reflect the new count that was passed to it?

We’re dealing with a function, so the child state should get blown away and replaced right? Wrong! The child’s state trumps the new prop from the parent. After the child component’s state is initialized in the first render, it’s completely independent from any props it receives.

React stores component state for each component in the tree and the state only gets blown away when the component is removed. Otherwise, the state won’t be affected by new props.

Using props to initialize state is called “derived state” and it is a bit of an anti-pattern. It removes the benefit of a component having a single source of truth for its data.

Using the key prop

But what if we have a collection of items we want to edit using the same type of child component, and we want the child to hold a draft of the item we’re editing? We’d need to reset the state of the child component each time we switch items from the collection.

Here’s an example: Let’s write an app where we can write a daily list of five thing’s we’re thankful for each day. We’ll use a parent with state initialized as an empty array which we’re going to fill up with five string statements.

Then we’ll have a a child component with a text input to enter our statement.

We’re about to use a criminal level of over-engineering in our tiny app, but it’s to illustrate a pattern you might need in a more complicated project: We’re going to hold the draft state of the text input in the child component.

Lowering the state to the child component can be a performance optimization to prevent the parent re-rendering when the input state changes. Otherwise the parent component will re-render every time there is a change in the text input.

We’ll also pass down an example statement as a default value for each of the five notes we’ll write.

Here’s a buggy way to do this:

// These are going to be our default values for each of the five notes // To give the user an idea of what they might write const ideaList = ["I'm thankful for my friends",                   "I'm thankful for my family",                   "I'm thankful for my health",                   "I'm thankful for my hobbies",                   "I'm thankful for CSS Tricks Articles"]  const maxStatements = 5;  function Parent(){   const [list,setList] = useState([]);      // Handler function for when the statement is completed   // Sets state providing a new array combining the current list and the new item    function onStatementComplete(payload){     setList(list=>[...list,payload]);   }   // Function to reset the list back to an empty array    function reset(){     setList([]);   }   return <div>     <h1>Your thankful list</h1>     <p>A five point list of things you're thankful for:</p>      {/* First we list the statements that have been completed*/}     {list.map((item,index)=>{return <p>Item {index+1}: {item}</p>})}      {/* If the length of the list is under our max statements length, we render      the statement form for the user to enter a new statement.     We grab an example statement from the idealist and pass down the onStatementComplete function.     Note: This implementation won't work as expected*/}     {list.length<maxStatements ?        <StatementForm initialStatement={ideaList[list.length]} onStatementComplete={onStatementComplete}/>       :<button onClick={reset}>Reset</button>     }   </div>; }  // Our child StatementForm component This accepts the example statement for it's initial state and the on complete function function StatementForm({initialStatement,onStatementComplete}){    // We hold the current state of the input, and set the default using initialStatement prop  const [statement,setStatement] = useState(initialStatement);    return <div>     {/*On submit we prevent default and fire the onStatementComplete function received via props*/}     <form onSubmit={(e)=>{e.preventDefault(); onStatementComplete(statement)}}>     <label htmlFor="statement-input">What are you thankful for today?</label><br/>     {/* Our controlled input below*/}     <input id="statement-input" onChange={(e)=>setStatement(e.target.value)} value={statement} type="text"/>     <input type="submit"/>       </form>   </div> }

There’s a problem with this: each time we submit a completed statement, the input incorrectly holds onto the submitted note in the textbox. We want to replace it with an example statement from our list.

Even though we’re passing down a different example string every time, the child remembers the old state and our newer prop is ignored. You could potentially check whether the props have changed on every render in a useEffect, and then reset the state if they have. But that can cause bugs when different parts of your data use the same values and you want to force the child state to reset even though the prop remains the same.

The solution

If you need a child component where the parent needs the ability to reset the child on demand, there is a way to do it: it’s by changing the key prop on the child.

You might have seen this special key prop from when you’re rendering elements based on an array and React throws a warning asking you to provide a key for each element. Changing the key of a child element ensures React creates a brand new version of the element. It’s a way of telling React that you are rendering a conceptually different item using the same component.

Let’s add a key prop to our child component. The value is the index we’re about to fill with our statement:

<StatementForm key={list.length} initialStatement={ideaList[list.length]} onStatementComplte={onStatementComplete}/>

Here’s what this looks like in our list app:

Note the only thing that changed here is that the child component now has a key prop based on the array index we’re about to fill. Yet, the behavior of the component has completely changed.

Now each time we submit and finish writing out statement, the old state in the child component gets thrown away and replaced with the example statement.

Buggy code #3: Stale closure bugs

This is a common issue with React hooks. There’s previously been a CSS-Tricks article about dealing with stale props and states in React’s functional components.

Let’s take a look at a few situations where you might run into trouble. The first crops up is when using useEffect. If we’re doing anything asynchronous inside of useEffect we can get into trouble using old state or props.

Here’s an example. We need to increment a count every second. We set it up on the first render with a useEffect, providing a closure that increments the count as the first argument, and an empty array as the second argument. We’ll give it the empty array as we don’t want React to restart the interval on every render.

function Counter() {    let [count, setCount] = useState(0);    useEffect(() => {     let id = setInterval(() => {       setCount(count + 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }

Oh no! The count gets incremented to 1 but never changes after that! Why is this happening?

It’s to do with two things:

Having a look at the MDN docs on closures, we can see:

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

The “lexical environment” in which our useEffect closure is declared is inside our Counter React component. The local variable we’re interested is count, which is zero at the time of the declaration (the first render).

The problem is, this closure is never declared again. If the count is zero at the time declaration, it will always be zero. Each time the interval fires, it’s running a function that starts with a count of zero and increments it to 1.

So how might we get the function declared again? This is where the second argument of the useEffect call comes in. We thought we were extremely clever only starting off the interval once by using the empty array, but in doing so we shot ourselves in the foot. If we had left out this argument, the closure inside useEffect would get declared again with a new count every time.

The way I like to think about it is that the useEffect dependency array does two things:

  • It will fire the useEffect function when the dependency changes.
  • It will also redeclare the closure with the updated dependency, keeping the closure safe from stale state or props.

In fact, there’s even a lint rule to keep your useEffect instances safe from stale state and props by making sure you add the right dependencies to the second argument.

But we don’t actually want to reset our interval every time the component gets rendered either. How do we solve this problem then?

The solution

Again, there are multiple solutions to our problem here. Let’s start with the easiest: not using the count state at all and instead passing a function into our setState call:

function Counter() {    let [count, setCount] = useState(0);    useEffect(() => {     let id = setInterval(() => {       setCount(prevCount => prevCount+ 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }

That was easy. Another option is to use the useRef hook like this to keep a mutable reference of the count:

function Counter() {   let [count, setCount] = useState(0);   const countRef = useRef(count)      function updateCount(newCount){     setCount(newCount);     countRef.current = newCount;   }    useEffect(() => {     let id = setInterval(() => {       updateCount(countRef.current + 1);     }, 1000);     return () => clearInterval(id);   },[]);    return <h1>{count}</h1>; }  ReactDOM.render(<Counter/>,document.getElementById("root"))

To go more in depth on using intervals and hooks you can take a look at this article about creating a useInterval in React by Dan Abramov, who is one of the React core team members. He takes a different route where, instead of holding the count in a ref, he places the entire closure in a ref.

To go more in depth on useEffect you can have a look at his post on useEffect.

More stale closure bugs

But stale closures won’t just appear in useEffect. They can also turn up in event handlers and other closures inside your React components. Let’s have a look at a React component with a stale event handler; we’ll create a scroll progress bar that does the following:

  • increases its width along the screen as the user scrolls
  • starts transparent and becomes more and more opaque as the user scrolls
  • provides the user with a button that randomizes the color of the scroll bar

We’re going to leave the progress bar outside of the React tree and update it in the event handler. Here’s our buggy implementation:

<body> <div id="root"></div> <div id="progress"></div> </body>
function Scroller(){    // We'll hold the scroll position in one state   const [scrollPosition, setScrollPosition] = useState(window.scrollY);   // And the current color in another   const [color,setColor] = useState({r:200,g:100,b:100});      // We assign out scroll listener on the first render   useEffect(()=>{    document.addEventListener("scroll",handleScroll);     return ()=>{document.removeEventListener("scroll",handleScroll);}   },[]);      // A function to generate a random color. To make sure the contrast is strong enough   // each value has a minimum value of 100   function onColorChange(){     setColor({r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155});   }      // This function gets called on the scroll event   function handleScroll(e){     // First we get the value of how far down we've scrolled     const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;     // Now we grab the height of the entire document     const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;      // And use these two values to figure out how far down the document we are     const percentAlong =  (scrollDistance / documentHeight);     // And use these two values to figure out how far down the document we are     const progress = document.getElementById("progress");     progress.style.width = `$  {percentAlong*100}%`;     // Here's where our bug is. Resetting the color here will mean the color will always      // be using the original state and never get updated     progress.style.backgroundColor = `rgba($  {color.r},$  {color.g},$  {color.b},$  {percentAlong})`;     setScrollPosition(percentAlong);   }      return <div className="scroller" style={{backgroundColor:`rgb($  {color.r},$  {color.g},$  {color.b})`}}>     <button onClick={onColorChange}>Change color</button>     <span class="percent">{Math.round(scrollPosition* 100)}%</span>   </div> }  ReactDOM.render(<Scroller/>,document.getElementById("root"))

Our bar gets wider and increasingly more opaque as the page scrolls. But if you click the change color button, our randomized colors are not affecting the progress bar. We’re getting this bug because the closure is affected by component state, and this closure is never being re-declared so we only get the original value of the state and no updates.

You can see how setting up closures that call external APIs using React state, or component props might give you grief if you’re not careful.

The solution

Again, there are multiple ways to fix this problem. We could keep the color state in a mutable ref which we could later use in our event handler:

const [color,setColor] = useState({r:200,g:100,b:100}); const colorRef = useRef(color);  function onColorChange(){   const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};   setColor(newColor);   colorRef.current=newColor;   progress.style.backgroundColor = `rgba($  {newColor.r},$  {newColor.g},$  {newColor.b},$  {scrollPosition})`; }

This works well enough but it doesn’t feel ideal. You may need to write code like this if you’re dealing with third-party libraries and you can’t find a way to pull their API into your React tree. But by keeping one of our elements out of the React tree and updating it inside of our event handler, we’re swimming against the tide.

This is a simple fix though, as we’re only dealing with the DOM API. An easy way to refactor this is to include the progress bar in our React tree and render it in JSX allowing it to reference the component’s state. Now we can use the event handling function purely for updating state.

function Scroller(){   const [scrollPosition, setScrollPosition] = useState(window.scrollY);   const [color,setColor] = useState({r:200,g:100,b:100});      useEffect(()=>{    document.addEventListener("scroll",handleScroll);     return ()=>{document.removeEventListener("scroll",handleScroll);}   },[]);      function onColorChange(){     const newColor = {r:100+Math.random()*155,g:100+Math.random()*155,b:100+Math.random()*155};     setColor(newColor);   }    function handleScroll(e){     const scrollDistance = document.body.scrollTop || document.documentElement.scrollTop;     const documentHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;     const percentAlong =  (scrollDistance / documentHeight);     setScrollPosition(percentAlong);   }   return <>     <div class="progress" id="progress"    style={{backgroundColor:`rgba($  {color.r},$  {color.g},$  {color.b},$  {scrollPosition})`,width: `$  {scrollPosition*100}%`}}></div>     <div className="scroller" style={{backgroundColor:`rgb($  {color.r},$  {color.g},$  {color.b})`}}>     <button onClick={onColorChange}>Change color</button>     <span class="percent">{Math.round(scrollPosition * 100)}%</span>   </div>   </> }

That feels better. Not only have we removed the chance for our event handler to get stale, we’ve also converted our progress bar into a self contained component which takes advantage of the declarative nature of React.

Also, for a scroll indicator like this, you might not even need JavaScript — have take a look at the up-and-coming @scroll-timeline CSS function or an approach using a gradient from Chris’ book on the greatest CSS tricks!

Wrapping up

We’ve had a look at three different ways you can create bugs in your React applications and some ways to fix them. It can be easy to look at counter examples which follow a happy path and don’t show subtleties in the APIs that might cause problems.

If you still find yourself needing to build a stronger mental model of what your React code is doing, here’s a list of resources which can help:


The post Three Buggy React Code Examples and How to Fix Them appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Links on React and JavaScript

As a day-job, React-using person, I like to stay abreast of interesting React news. As such, I save a healthy amount of links. Allow me to dump out my latest pile. Most of this is about React but not all of it.

  • The Plan for React 18 — A bunch of people from the React team put this post out giving us all a heads up of what’s coming. Alpha is out, beta is months away. I thought Cassidy’s article on it was the most clear about what we’re likely to care about.
  • React Query — Looks like a pretty robust tool… “the missing data-fetching library for React.” Don’t know how I missed it as it even seems more popular than Apollo. I’ve been pretty happy with using Apollo (as a user, my biggest pain is unclear error reporting), and it seems like that’s probabably the right choice if you’re heavy into GraphQL, but React Query looks awfully nice with clear docs and nice DevTools.
  • Data Fetching in Redux Made Easy With RTK Query — Matt Stobbs looks at RTK Query, which looks like yet another alternative to the Apollo / React Query stuff. Take a look at the Redux store in an app you’re working on now. If it’s anything like mine, you’ll see a mix of data from the backend (which is behaving as a cache) and UI state (the data that isn’t persisted when the page reloads). These two types of data are treated as if they are the same, which ends up making both more complicated.
  • Just-In-Time translations and code that writes itself — Dan Laush looks at a bunch of modern options for conditional and lazy loading JavaScript. This stuff is probably more complicated than it should be, but it’s getting better. Suspense in React 18 will be helpful. Top-level await is helpful. Load what you need when you need it. Astro is good at this. And, speaking of all this, Nicholas C. Zakas’ “The lazy-loading property pattern in JavaScript” is a great read with a clever pattern for defining objects that only do expensive things once, lazily when asked, then redefine that property on themselves with the result.
  • Bringing JSX to Template Literals — People think of JSX as a React thing, which is kinda fair, but it’s really a separate thing that can be useful with other frameworks (certainly Preact and even Vue). We looked at how it can be fun with even no framework at all in a previous video. Andrea Giammarchi goes deep here and shows how it can work with the already nicely-ergnomic template literals. “You can see it working in CodePen via uhtmlulandube, or lit-html.”
  • React Hooks: Compound Components — Shout out to Kent Dodds! We’ve started using this in our pattern library at CodePen. It’s been nice for keeping components a bit more consolidated rather than a sprawling tree of similarly-named sub components with hand-rolled state sharing.
  • JavaScript: What is the meaning of this? — Jake Archibald puts out the canonical article on this.
  • Human-Readable JavaScript: A Tale of Two Experts — Laurie Barth compares examples of code that do the same thing, but have different levels of readability. There isn’t always a straight answer “… but when you’re looking at code that is functionally identical, your determination should be based on humans—how humans consume code.”
  • petite-vue — jQuery was amazing and there is plenty of perfectly fine jQuery code, but the reason jQuery is a bit looked down upon these days is the messy code bases that were made with it. Some lessons were learned. While inline JavaScript handlers were once heavily scorned, nearly every popular JavaScript library today has brought them back. But let’s say something like React is too heavy-handed for you—what is the jQuery of light on-page interactivity stuff? Vue sort of walks the line between that and being more of a “big framework.” Alpine.js is probably the main player. But here comes Vue again with a poke at Alpine with a version of itself that is pretty darn small and does the same sort of stuff.

The post Links on React and JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Svelte for the Experienced React Dev

This post is an accelerated introduction to Svelte from the point of view of someone with solid experience with React. I’ll provide a quick introduction, and then shift focus to things like state management and DOM interoperability, among other things. I plan on moving somewhat quickly, so I can cover a lot of topics. At the end of the day, I’m mainly hoping to spark some interest in Svelte.

For a straightforward introduction to Svelte, no blog post could ever beat the official tutorial or docs.

“Hello, World!” Svelte style

Let’s start with a quick tour of what a Svelte component looks like.

<script>   let number = 0; </script>  <style>   h1 {     color: blue;   } </style>  <h1>Value: {number}</h1>  <button on:click={() => number++}>Increment</button> <button on:click={() => number--}>Decrement</button> 

That content goes in a .svelte file, and is processed by the Rollup or webpack plugin to produce a Svelte component. There’s a few pieces here. Let’s walk through them.

First, we add a <script> tag with any state we need.

We can also add a <style> tag with any CSS we want. These styles are scoped to the component in such a way that, here, <h1> elements in this component will be blue. Yes, scoped styles are built into Svelte, without any need for external libraries. With React, you’d typically need to use a third-party solution to achieve scoped styling, such as css-modules, styled-components, or the like (there are dozens, if not hundreds, of choices).

Then there’s the HTML markup. As you’d expect, there are some HTML bindings you’ll need to learn, like {#if}, {#each}, etc. These domain-specific language features might seem like a step back from React, where everything is “just JavaScript.” But there’s a few things worth noting: Svelte allows you to put arbitrary JavaScript inside of these bindings. So something like this is perfectly valid:

{#if childSubjects?.length}

If you jumped into React from Knockout or Ember and never looked back, this might come as a (happy) surprise to you.

Also, the way Svelte processes its components is very different from React. React re-runs all components any time any state within a component, or anywhere in an ancestor (unless you “memoize”), changes. This can get inefficient, which is why React ships things like useCallback and useMemo to prevent un-needed re-calculations of data.

Svelte, on the other hand, analyzes your template, and creates targeted DOM update code whenever any relevant state changes. In the component above, Svelte will see the places where number changes, and add code to update the <h1> text after the mutation is done. This means you never have to worry about memoizing functions or objects. In fact, you don’t even have to worry about side-effect dependency lists, although we’ll get to that in a bit.

But first, let’s talk about …

State management

In React, when we need to manage state, we use the useState hook. We provide it an initial value, and it returns a tuple with the current value, and a function we can use to set a new value. It looks something like this:

import React, { useState } from "react";  export default function (props) {   const [number, setNumber] = useState(0);   return (     <>       <h1>Value: {number}</h1>       <button onClick={() => setNumber(n => n + 1)}>Increment</button>       <button onClick={() => setNumber(n => n - 1)}>Decrement</button>     </>   ); }

Our setNumber function can be passed wherever we’d like, to child components, etc.

Things are simpler in Svelte. We can create a variable, and update it as needed. Svelte’s ahead-of-time compilation (as opposed to React’s just-in-time compilation) will do the footwork of tracking where it’s updated, and force an update to the DOM. The same simple example from above might look like this:

<script>   let number = 0; </script>  <h1>Value: {number}</h1> <button on:click={() => number++}>Increment</button> <button on:click={() => number--}>Decrement</button>

Also of note here is that Svelte requires no single wrapping element like JSX does. Svelte has no equivalent of the React fragment <></> syntax, since it’s not needed.

But what if we want to pass an updater function to a child component so it can update this piece of state, like we can with React? We can just write the updater function like this:

<script>   import Component3a from "./Component3a.svelte";            let number = 0;   const setNumber = cb => number = cb(number); </script>  <h1>Value: {number}</h1>  <button on:click={() => setNumber(val => val + 1)}>Increment</button> <button on:click={() => setNumber(val => val - 1)}>Decrement</button>

Now, we pass it where needed — or stay tuned for a more automated solution.

Reducers and stores

React also has the useReducer hook, which allows us to model more complex state. We provide a reducer function, and it gives us the current value, and a dispatch function that allows us to invoke the reducer with a given argument, thereby triggering a state update, to whatever the reducer returns. Our counter example from above might look like this:

import React, { useReducer } from "react";  function reducer(currentValue, action) {   switch (action) {     case "INC":       return currentValue + 1;     case "DEC":       return currentValue - 1;   } }  export default function (props) {   const [number, dispatch] = useReducer(reducer, 0);   return (     <div>       <h1>Value: {number}</h1>       <button onClick={() => dispatch("INC")}>Increment</button>       <button onClick={() => dispatch("DEC")}>Decrement</button>     </div>   ); }

Svelte doesn’t directly have something like this, but what it does have is called a store. The simplest kind of store is a writable store. It’s an object that holds a value. To set a new value, you can call set on the store and pass the new value, or you can call update, and pass in a callback function, which receives the current value, and returns the new value (exactly like React’s useState).

To read the current value of a store at a moment in time, there’s a get function that can be called, which returns its current value. Stores also have a subscribe function, which we can pass a callback to, and that will run whenever the value changes.

Svelte being Svelte, there’s some nice syntactic shortcuts to all of this. If you’re inside of a component, for example, you can just prefix a store with the dollar sign to read its value, or directly assign to it, to update its value. Here’s the counter example from above, using a store, with some extra side-effect logging, to demonstrate how subscribe works:

<script>   import { writable, derived } from "svelte/store";            let writableStore = writable(0);   let doubleValue = derived(writableStore, $  val => $  val * 2);            writableStore.subscribe(val => console.log("current value", val));   doubleValue.subscribe(val => console.log("double value", val)) </script>  <h1>Value: {$  writableStore}</h1>  <!-- manually use update --> <button on:click={() => writableStore.update(val => val + 1)}>Increment</button> <!-- use the $   shortcut --> <button on:click={() => $  writableStore--}>Decrement</button>  <br />  Double the value is {$  doubleValue}

Notice that I also added a derived store above. The docs cover this in depth, but briefly, derived stores allow you to project one store (or many stores) to a single, new value, using the same semantics as a writable store.

Stores in Svelte are incredibly flexible. We can pass them to child components, alter, combine them, or even make them read-only by passing through a derived store; we can even re-create some of the React abstractions you might like, or even need, if we’re converting some React code over to Svelte.

React APIs with Svelte

With all that out of the way, let’s return to React’s useReducer hook from before.

Let’s say we really like defining reducer functions to maintain and update state. Let’s see how difficult it would be to leverage Svelte stores to mimic React’s useReducer API. We basically want to call our own useReducer, pass in a reducer function with an initial value, and get back a store with the current value, as well as a dispatch function that invokes the reducer and updates our store. Pulling this off is actually not too bad at all.

export function useReducer(reducer, initialState) {   const state = writable(initialState);   const dispatch = (action) =>     state.update(currentState => reducer(currentState, action));   const readableState = derived(state, ($  state) => $  state);    return [readableState, dispatch]; }

The usage in Svelte is almost identical to React. The only difference is that our current value is a store, rather than a raw value, so we need to prefix it with the $ to read the value (or manually call get or subscribe on it).

<script>   import { useReducer } from "./useReducer";            function reducer(currentValue, action) {     switch (action) {       case "INC":         return currentValue + 1;       case "DEC":         return currentValue - 1;     }   }   const [number, dispatch] = useReducer(reducer, 0);       </script>  <h1>Value: {$  number}</h1>  <button on:click={() => dispatch("INC")}>Increment</button> <button on:click={() => dispatch("DEC")}>Decrement</button>

What about useState?

If you really love the useState hook in React, implementing that is just as straightforward. In practice, I haven’t found this to be a useful abstraction, but it’s a fun exercise that really shows Svelte’s flexibility.

export function useState(initialState) {   const state = writable(initialState);   const update = (val) =>     state.update(currentState =>       typeof val === "function" ? val(currentState) : val     );   const readableState = derived(state, $  state => $  state);    return [readableState, update]; }

Are two-way bindings really evil?

Before closing out this state management section, I’d like to touch on one final trick that’s specific to Svelte. We’ve seen that Svelte allows us to pass updater functions down the component tree in any way that we can with React. This is frequently to allow child components to notify their parents of state changes. We’ve all done it a million times. A child component changes state somehow, and then calls a function passed to it from a parent, so the parent can be made aware of that state change.

In addition to supporting this passing of callbacks, Svelte also allows a parent component to two-way bind to a child’s state. For example, let’s say we have this component:

<!-- Child.svelte --> <script>   export let val = 0; </script>  <button on:click={() => val++}>   Increment </button>  Child: {val}

This creates a component, with a val prop. The export keyword is how components declare props in Svelte. Normally, with props, we pass them in to a component, but here we’ll do things a little differently. As we can see, this prop is modified by the child component. In React this code would be wrong and buggy, but with Svelte, a component rendering this component can do this:

<!-- Parent.svelte --> <script>   import Child from "./Child.svelte";            let parentVal; </script>  <Child bind:val={parentVal} /> Parent Val: {parentVal}

Here, we’re binding a variable in the parent component, to the child’s val prop. Now, when the child’s val prop changes, our parentVal will be updated by Svelte, automatically.

Two-way binding is controversial for some. If you hate this then, by all means, feel free to never use it. But used sparingly, I’ve found it to be an incredibly handy tool to reduce boilerplate.

Side effects in Svelte, without the tears (or stale closures)

In React, we manage side effects with the useEffect hook. It looks like this:

useEffect(() => {   console.log("Current value of number", number); }, [number]);

We write our function with the dependency list at the end. On every render, React inspects each item in the list, and if any are referentially different from the last render, the callback re-runs. If we’d like to cleanup after the last run, we can return a cleanup function from the effect.

For simple things, like a number changing, it’s easy. But as any experienced React developer knows, useEffect can be insidiously difficult for non-trivial use cases. It’s surprisingly easy to accidentally omit something from the dependency array and wind up with a stale closure.

In Svelte, the most basic form of handling a side effect is a reactive statement, which looks like this:

$  : {   console.log("number changed", number); }

We prefix a code block with $ : and put the code we’d like to execute inside of it. Svelte analyzes which dependencies are read, and whenever they change, Svelte re-runs our block. There’s no direct way to have the cleanup run from the last time the reactive block was run, but it’s easy enough to workaround if we really need it:

let cleanup; $  : {   cleanup?.();   console.log("number changed", number);   cleanup = () => console.log("cleanup from number change"); }

No, this won’t lead to an infinite loop: re-assignments from within a reactive block won’t re-trigger the block.

While this works, typically these cleanup effects need to run when your component unmounts, and Svelte has a feature built in for this: it has an onMount function, which allows us to return a cleanup function that runs when the component is destroyed, and more directly, it also has an onDestroy function that does what you’d expect.

Spicing things up with actions

The above all works well enough, but Svelte really shines with actions. Side effects are frequently tied to our DOM nodes. We might want to integrate an old (but still great) jQuery plugin on a DOM node, and tear it down when that node leaves the DOM. Or maybe we want to set up a ResizeObserver for a node, and tear it down when the node leaves the DOM, and so on. This is a common enough requirement that Svelte builds it in with actions. Let’s see how.

{#if show}   <div use:myAction>     Hello                   </div> {/if}

Note the use:actionName syntax. Here we’ve associated this <div> with an action called myAction, which is just a function.

function myAction(node) {   console.log("Node added", node); }

This action runs whenever the <div> enters the DOM, and passes the DOM node to it. This is our chance to add our jQuery plugins, set up our ResizeObserver, etc. Not only that, but we can also return a cleanup function from it, like this:

function myAction(node) {   console.log("Node added", node);    return {     destroy() {       console.log("Destroyed");     }   }; }

Now the destroy() callback will run when the node leaves the DOM. This is where we tear down our jQuery plugins, etc.

But wait, there’s more!

We can even pass arguments to an action, like this:

<div use:myAction={number}>   Hello                 </div>

That argument will be passed as the second argument to our action function:

function myAction(node, param) {   console.log("Node added", node, param);    return {     destroy() {       console.log("Destroyed");     }   }; }

And if you’d like to do additional work whenever that argument changes, you can return an update function:

function myAction(node, param) {   console.log("Node added", node, param);    return {     update(param) {       console.log("Update", param);     },     destroy() {       console.log("Destroyed");     }   }; }

When the argument to our action changes, the update function will run. To pass multiple arguments to an action, we pass an object:

<div use:myAction={{number, otherValue}}>   Hello                 </div>

…and Svelte re-runs our update function whenever any of the object’s properties change.

Actions are one of my favorite features of Svelte; they’re incredibly powerful.

Odds and Ends

Svelte also ships a number of great features that have no counterpart in React. There’s a number of form bindings (which the tutorial covers), as well as CSS helpers.

Developers coming from React might be surprised to learn that Svelte also ships animation support out of the box. Rather than searching on npm and hoping for the best, it’s… built in. It even includes support for spring physics, and enter and exit animations, which Svelte calls transitions.

Svelte’s answer to React.Chidren are slots, which can be named or not, and are covered nicely in the Svelte docs. I’ve found them much simpler to reason about than React’s Children API.

Lastly, one of my favorite, almost hidden features of Svelte is that it can compile its components into actual web components. The svelte:options helper has a tagName property that enables this. But be sure to set the corresponding property in the webpack or Rollup config. With webpack, it would look something like this:

{   loader: "svelte-loader",   options: {     customElement: true   } }

Interested in giving Svelte a try?

Any of these items would make a great blog post in and of itself. While we may have only scratched the surface of things like state management and actions, we saw how Svelte’s features not only match up pretty with React, but can even mimic many of React’s APIs. And that’s before we briefly touched on Svelte’s conveniences, like built-in animations (or transitions) and the ability to convert Svelte components into bona fide web components.

I hope I’ve succeeded in sparking some interest, and if I have, there’s no shortage of docs, tutorials, online courses, etc that dive into these topics (and more). Let me know in the comments if you have any questions along the way!


The post Svelte for the Experienced React Dev appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

React Authentication & Access Control

Authentication and access control are required for most applications, but they often distract us from building core features. In this article, I’ll cover a straightforward way to add auth and access control in React.

Instead of adding a static library that you have to keep up to date or re-research each time you build a project, we’ll use a service that stays up to date automatically and is a much simpler alternative to Auth0, Okta, and others.

React authentication

There are two main things your React application needs to do to sign on a user:

  1. Get an access token from an authentication server
  2. Send the access token to your backend server with each subsequent request

These steps are the same for pretty much all authentication, whether that’s standard email and password, magic links, or single sign on (SSO) providers like Google, Azure, or Facebook.

Ultimately, we want our React app to send an initial request to an authentication server and have that server generate an access token we can use.

JWT access tokens

There are different choices for what type of access token to use, and JSON Web Tokens (JWTs) are a great option. JWTs are compact, URL-safe tokens that your React application can use for authentication and access control.

Each JWT has a JSON object as its “payload” and is signed such that your backend server can verify that the payload is authentic. An example JWT looks like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9.f7iKN-xi24qrQ5NQtOe0jiriotT-rve3ru6sskbQXnA 

The payload for this JWT is the middle section (separated by periods):

eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9

The JWT payload can be decoded from base64 to yield the JSON object:

JSON.parse(atob("eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9"));  // => {   “userId”: 1,   “authorization”: “admin” }

It’s important to note that this payload is readable by anyone with the JWT, including your React application or a third party. Anyone that has the JWT can read its contents.

However, only the authentication server can generate valid JWTs. Your React application, your backend server, or a malicious third party cannot generate valid JWTs, only read and verify them.

When your backend server receives a request with a JWT, it should verify the JWT as authentic by checking it against the public key for that JWT. This allows your application server to verify incoming JWTs and reject any tokens that were not created by the authentication server (or that have expired).

The flow for using a JWT in your React application looks like this:

  1. Your React app requests a JWT from the authentication server whenever the user wants to sign on.
  2. The authentication server generates a JWT using a private key and then sends the JWT back to your React app.
  3. Your React app stores this JWT and sends it to your backend server whenever your user needs to make a request.
  4. Your backend server verifies the JWT using a public key and then reads the payload to determine which user is making the request.

Each of these steps is simple to write down, but each step has its own pitfalls when you actually want to implement it and keep it secure. Especially over time, as new threat vectors emerge and new platforms need to be patched or supported, the security overhead can add up quickly.

Userfront removes auth complexity in React apps

Userfront is a framework that abstracts away auth complexity. This makes it much easier for you to work with authentication in a React application and, perhaps most importantly, it keeps all the auth protocols updated for you automatically over time.

The underlying philosophy with Userfront is that world-class auth should not take effort – it should be easy to set up, and security updates should happen for you automatically. Userfront has all the bells and whistles of authentication, Single Sign On (SSO), access control, and multi-tenancy, with a production-ready free tier up to 10,000 monthly active users.

For most modern React applications, it’s a great solution.

Setting up authentication in React

Now we’ll go through building all the main aspects of authentication in a React application. The final code for this example is available here.

Set up your React application and get your build pipeline in order however you prefer. In this tutorial, we’ll use Create React App, which does a lot of the setup work for us, and we’ll also add React Router for client-side routing. Start by installing Create React App and React Router:

npx create-react-app my-app cd my-app npm install react-router-dom --save npm start

Now our React application is available at http://localhost:3000.

Like the page says, we can now edit the src/App.js file to start working.

Replace the contents of src/App.js with the following, based on the React Router quickstart:

// src/App.js  import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";  export default function App() {   return (     <Router>       <div>         <nav>           <ul>             <li>               <Link to="/">Home</Link>             </li>             <li>               <Link to="/login">Login</Link>             </li>             <li>               <Link to="/reset">Reset</Link>             </li>             <li>               <Link to="/dashboard">Dashboard</Link>             </li>           </ul>         </nav>          <Switch>           <Route path="/login">             <Login />           </Route>           <Route path="/reset">             <PasswordReset />           </Route>           <Route path="/dashboard">             <Dashboard />           </Route>           <Route path="/">             <Home />           </Route>         </Switch>       </div>     </Router>   ); }  function Home() {   return <h2>Home</h2>; }  function Login() {   return <h2>Login</h2>; }  function PasswordReset() {   return <h2>Password Reset</h2>; }  function Dashboard() {   return <h2>Dashboard</h2>; }

Now we have ourselves a very simple app with routing:

Route Description
/ Home page
/login Login page
/reset Password reset page
/dashboard User dashboard, for logged in users only

This is all the structure we need to start adding authentication.

Signup, login, and password reset with Userfront

Go ahead and create a Userfront account. This will give you a signup form, login form, and password reset form you can use for the next steps.

In the Toolkit section of your Userfront dashboard, you can find the instructions for installing your signup form:

Follow the instructions by installing the Userfront React package with:

npm install @userfront/react --save npm start

Add the signup form to your home page by importing and initializing Userfront, and then updating the Home() function to render the signup form.

// src/App.js  import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import Userfront from "@userfront/react";  Userfront.init("demo1234");  const SignupForm = Userfront.build({   toolId: "nkmbbm", });  export default function App() {   return (     <Router>       <div>         <nav>           <ul>             <li>               <Link to="/">Home</Link>             </li>             <li>               <Link to="/login">Login</Link>             </li>             <li>               <Link to="/reset">Reset</Link>             </li>             <li>               <Link to="/dashboard">Dashboard</Link>             </li>           </ul>         </nav>          <Switch>           <Route path="/login">             <Login />           </Route>           <Route path="/reset">             <PasswordReset />           </Route>           <Route path="/dashboard">             <Dashboard />           </Route>           <Route path="/">             <Home />           </Route>         </Switch>       </div>     </Router>   ); }  function Home() {   return (     <div>       <h2>Home</h2>       <SignupForm />     </div>   ); }  function Login() {   return <h2>Login</h2>; }  function PasswordReset() {   return <h2>Password Reset</h2>; }  function Dashboard() {   return <h2>Dashboard</h2>; }

Now the home page has your signup form. Try signing up a user:

Your signup form is in “Test mode” by default, which will create user records in a test environment you can view separately in your Userfront dashboard:

Continue by adding your login and password reset forms in the same way that you added your signup form:

// src/App.js  import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; import Userfront from "@userfront/react";  Userfront.init("demo1234");  const SignupForm = Userfront.build({   toolId: "nkmbbm", }); const LoginForm = Userfront.build({   toolId: "alnkkd", }); const PasswordResetForm = Userfront.build({   toolId: "dkbmmo", });  export default function App() {   return (     <Router>       <div>         <nav>           <ul>             <li>               <Link to="/">Home</Link>             </li>             <li>               <Link to="/login">Login</Link>             </li>             <li>               <Link to="/reset">Reset</Link>             </li>             <li>               <Link to="/dashboard">Dashboard</Link>             </li>           </ul>         </nav>          <Switch>           <Route path="/login">             <Login />           </Route>           <Route path="/reset">             <PasswordReset />           </Route>           <Route path="/dashboard">             <Dashboard />           </Route>           <Route path="/">             <Home />           </Route>         </Switch>       </div>     </Router>   ); }  function Home() {   return (     <div>       <h2>Home</h2>       <SignupForm />     </div>   ); }  function Login() {   return (     <div>       <h2>Login</h2>       <LoginForm />     </div>   ); }  function PasswordReset() {   return (     <div>       <h2>Password Reset</h2>       <PasswordResetForm />     </div>   ); }  function Dashboard() {   return <h2>Dashboard</h2>; }

At this point, your signup, login, and password reset should all be functional.

Your users can sign up, log in, and reset their password.

Access control in React

Usually, we don’t want users to be able to view the dashboard unless they are logged in. This is known as a protected route.

Whenever a user is not logged in but tries to visit /dashboard, we can redirect them to the login screen.

We can accomplish this by updating the Dashboard component in src/App.js to handle the conditional logic.

When a user is logged in with Userfront, they will have an access token available as Userfront.accessToken(). We can check for this token to determine if the user is logged in. If the user is logged in, we can show the dashboard page, and if the user is not logged in, we can redirect to the login page.

Add the Redirect component to the import statement for React Router, and then update the Dashboard component to redirect if no access token is present.

// src/App.js  import React from "react"; import {   BrowserRouter as Router,   Switch,   Route,   Link,   Redirect, // Be sure to add this import } from "react-router-dom";  // ...  function Dashboard() {   function renderFn({ location }) {     // If the user is not logged in, redirect to login     if (!Userfront.accessToken()) {       return (         <Redirect           to={{             pathname: "/login",             state: { from: location },           }}         />       );     }      // If the user is logged in, show the dashboard     const userData = JSON.stringify(Userfront.user, null, 2);     return (       <div>         <h2>Dashboard</h2>         <pre>{userData}</pre>         <button onClick={Userfront.logout}>Logout</button>       </div>     );   }    return <Route render={renderFn} />; }

Notice also that we’ve added a logout button by calling Userfront.logout() directly:

<button onClick={Userfront.logout}>Logout</button>

Now, when a user is logged in, they can view the dashboard. If the user is not logged in, they will be redirected to the login page.

React authentication with an API

You’ll probably want to retrieve user-specific information from your backend. In order to protect your API endpoints, your backend server should check that incoming JWTs are valid.

There are many libraries available to read and verify JWTs across various languages; here are a few popular libraries for handling JWTs:

Node.js .NET Python Java

Your access token

While the user is logged in, their access token is available in your React application as Userfront.accessToken().

Your React application can send this as a Bearer token inside the Authorization header to your backend server. For example:

// Example of calling an endpoint with a JWT  import Userfront from "@userfront/react"; Userfront.init("demo1234");  async function getInfo() {   const res = await window.fetch("/your-endpoint", {     method: "GET",     headers: {       "Content-Type": "application/json",       Authorization: `Bearer $ {Userfront.accessToken()}`,     },   });    console.log(res); }  getInfo();

To handle a request like this, your backend should read the JWT from the Authorization header and verify that it is valid using the public key found in your Userfront dashboard.

Here is an example of Node.js middleware to read and verify the JWT access token:

// Node.js example (Express.js)  const jwt = require("jsonwebtoken");  function authenticateToken(req, res, next) {   // Read the JWT access token from the request header   const authHeader = req.headers["authorization"];   const token = authHeader && authHeader.split(" ")[1];   if (token == null) return res.sendStatus(401); // Return 401 if no token    // Verify the token using the Userfront public key   jwt.verify(token, process.env.USERFRONT_PUBLIC_KEY, (err, auth) => {     if (err) return res.sendStatus(403); // Return 403 if there is an error verifying     req.auth = auth;     next();   }); }

Using this approach, any invalid or missing tokens would be rejected by your server. You can also reference the contents of the token later in the route handlers using the req.auth object:

console.log(req.auth);  // => {   mode: 'test',   tenantId: 'demo1234',   userId: 5,   userUuid: 'ab53dbdc-bb1a-4d4d-9edf-683a6ca3f609',   isConfirmed: false,   authorization: {     demo1234: {       tenantId: 'demo1234',       name: 'Demo project',       roles: ["admin"],       permissions: []     },   },   sessionId: '35d0bf4a-912c-4429-9886-cd65a4844a4f',   iat: 1614114057,   exp: 1616706057 }

With this information, you can perform further checks as desired, or use the userId or userUuid to look up user information to return.

For example, if you wanted to limit a route to admin users only, you could check against the authorization object from the verified access token, and reject any tokens that don’t have an admin role:

// Node.js example (Express.js)  app.get("/users", (req, res) => {   const authorization = req.auth.authorization["demo1234"] || {};    if (authorization.roles.includes("admin")) {     // Allow access   } else {     // Deny access   } });

React SSO (Single Sign On)

With your Toolkit forms in place, you can add social identity providers like Google, Facebook, and LinkedIn to your React application, or business identity providers like Azure AD, Office365, and more.

You do this by creating an application with the identity provider (e.g. Google), and then adding that application’s credentials to the Userfront dashboard. The result is a modified sign on experience:

No additional code is needed to implement Single Sign On using this approach: you can add and remove providers without updating your forms or the way you handle JWTs.

Final notes

React authentication and access control can be complex to do yourself, or it can be simple when using a service.

Both the setup step and, more importantly, the maintenance over time, are handled with modern platforms like Userfront.

JSON Web Tokens allow you to cleanly separate your auth token generation layer from the rest of your application, making it easier to reason about and more modular for future needs. This architecture also allows you to focus your efforts on your core application, where you are likely to create much more value for yourself or your clients.

For more details on adding auth to your React application, visit the Userfront guide, which covers everything from setting up your auth forms to API documentation, example repositories, working with different languages and frameworks, and more.


The post React Authentication & Access Control appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

React Without Build Tools

Jim Nielsen:

I think you’ll find it quite refreshing to use React A) with a JSX-like syntax, and B) without any kind of build tooling.

Refreshing indeed:

It’s not really the React that’s the hard part to pull off without build tools (although I do wonder what we lose from not tree shaking), it’s the JSX. I’m so used to JSX I think it would be hard for me to work on a front-end JavaScript project without it. But I know some people literally prefer a render function instead. If that’s the case, you could use React.createComponent directly and skip the JSX, or in the case of Preact, use h:

I work on a project that uses Mithril for the JavaScript templating which is a bit like that, and it’s not my favorite syntax, but you totally get used to it (and it’s fast):

Direct Link to ArticlePermalink


The post React Without Build Tools appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

React Component Tests for Humans

React component tests should be interesting, straightforward, and easy for a human to build and maintain.

Yet, the current state of the testing library ecosystem is not sufficient to motivate developers to write consistent JavaScript tests for React components. Testing React components—and the DOM in general—often require some kind of higher-level wrapper around popular testing frameworks like Jest or Mocha.

Here’s the problem

Writing component tests with the tools available today is boring, and even when you get to writing them, it takes lots of hassle. Expressing test logic following a jQuery-like style (chaining) is confusing. It doesn’t jive with how React components are usually built.

The Enzyme code below is readable, but a bit too bulky because it uses too many words to express something that is ultimately simple markup.

expect(screen.find(".view").hasClass("technologies")).to.equal(true); expect(screen.find("h3").text()).toEqual("Technologies:"); expect(screen.find("ul").children()).to.have.lengthOf(4); expect(screen.contains([   <li>JavaScript</li>,   <li>ReactJs</li>,   <li>NodeJs</li>,   <li>Webpack</li> ])).to.equal(true); expect(screen.find("button").text()).toEqual("Back"); expect(screen.find("button").hasClass("small")).to.equal(true);

The DOM representation is just this:

<div className="view technologies">   <h3>Technologies:</h3>   <ul>     <li>JavaScript</li>     <li>ReactJs</li>     <li>NodeJs</li>     <li>Webpack</li>   </ul>   <button className="small">Back</button> </div>

What if you need to test heavier components? While the syntax is still bearable, it doesn’t help your brain grasp the structure and logic. Reading and writing several tests like this is bound to wear you out—it certainly wears me out. That’s because React components follow certain principles to generate HTML code at the end. Tests that express the same principles, on the other hand, are not straightforward. Simply using JavaScript chaining won’t help in the long run.

There are two main issues with testing in React:

  • How to even approach writing tests specifically for components
  • How to avoid all the unnecessary noise

Let’s further expand those before jumping into the real examples.

Approaching React component tests

A simple React component may look like this:

function Welcome(props) {   return <h1>Hello, {props.name}</h1>; }

This is a function that accepts a props object and returns a DOM node using the JSX syntax.

Since a component can be represented by a function, it is all about testing functions. We need to account for arguments and how they influence the returned result. Applying that logic to React components, the focus in the tests should be on setting up props and testing for the DOM rendered in the UI. Since user actions like mouseover, click, typing, etc. may also lead to UI changes, you will need to find a way to programmatically trigger those too.

Hiding the unnecessary noise in tests

Tests require a certain level of readability achieved by both slimming the wording down and following a certain pattern to describe each scenario.

Component tests flow through three phases:

  1. Preparation (setup): The component props are prepared.
  2. Render (action): The component needs to render its DOM to the UI before either triggering any actions on it or testing for certain texts and attributes. That’s when actions can be programmatically triggered.
  3. Validation (verify): The expectations are set, verifying certain side effects over the component markup.

Here is an example:

it("should click a large button", () => {   // 1️⃣ Preparation   // Prepare component props   props.size = "large";    // 2️⃣ Render   // Render the Button's DOM and click on it   const component = mount(<Button {...props}>Send</Button>);   simulate(component, { type: "click" });    // 3️⃣ Validation   // Verify a .clicked class is added    expect(component, "to have class", "clicked"); });

For simpler tests, the phases can merge:

it("should render with a custom text", () => {   // Mixing up all three phases into a single expect() call   expect(     // 1️⃣ Preparation     <Button>Send</Button>,      // 2️⃣ Render     "when mounted",     // 3️⃣ Validation     "to have text",      "Send"   ); });

Writing component tests today

Those two examples above look logical but are anything but trivial. Most of the testing tools do not provide such a level of abstraction, so we have to handle it ourselves. Perhaps the code below looks more familiar.

it("should display the technologies view", () => {   const container = document.createElement("div");   document.body.appendChild(container);      act(() => {     ReactDOM.render(<ProfileCard {...props} />, container);   });      const button = container.querySelector("button");      act(() => {     button.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));   });      const details = container.querySelector(".details");      expect(details.classList.contains("technologies")).toBe(true);   expect(details.querySelector("h3").textContent, "to be", "Technologies");   expect(details.querySelector("button").textContent, "to be", "View Bio"); });

Compare that with the same test, only with an added layer of abstraction:

it("should display the technologies view", () => {   const component = mount(<ProfileCard {...props} />);    simulate(component, {     type: "click",     target: "button",   });    expect(     component,     "queried for first",     ".details",     "to exhaustively satisfy",     <div className="details technologies">       <h3>Technologies</h3>       <div>         <button>View Bio</button>       </div>     </div>   ); });

It does look much better. Less code, obvious flow, and more DOM instead of JavaScript. This is not a fiction test, but something you can achieve with UnexpectedJS today.

The following section is a deep dive into testing React components without getting too deep into UnexpectedJS. Its documentation more than does the job. Instead, we’ll focus on usage, examples, and possibilities.

Writing React Tests with UnexpectedJS

UnexpectedJS is an extensible assertion toolkit compatible with all test frameworks. It can be extended with plugins, and some of those plugins are used in the test project below. Probably the best thing about this library is the handy syntax it provides to describe component test cases in React.

The example: A Profile Card component

The subject of the tests is a Profile card component.

A card component where the persons name, photo, and number of posts are displayed to the left in a single column with a light red background, and the bio is displayed on the right in paragraph form with a title against a white background.

And here is the full component code of ProfileCard.js:

// ProfileCard.js export default function ProfileCard({   data: {     name,     posts,     isOnline = false,     bio = "",     location = "",     technologies = [],     creationDate,     onViewChange,   }, }) {   const [isBioVisible, setIsBioVisible] = useState(true);    const handleBioVisibility = () => {     setIsBioVisible(!isBioVisible);     if (typeof onViewChange === "function") {       onViewChange(!isBioVisible);     }   };    return (     <div className="ProfileCard">       <div className="avatar">         <h2>{name}</h2>         <i className="photo" />         <span>{posts} posts</span>         <i className={`status $ {isOnline ? "online" : "offline"}`} />       </div>       <div className={`details $ {isBioVisible ? "bio" : "technologies"}`}>         {isBioVisible ? (           <>             <h3>Bio</h3>             <p>{bio !== "" ? bio : "No bio provided yet"}</p>             <div>               <button onClick={handleBioVisibility}>View Skills</button>               <p className="joined">Joined: {creationDate}</p>             </div>           </>         ) : (           <>             <h3>Technologies</h3>             {technologies.length > 0 && (               <ul>                 {technologies.map((item, index) => (                   <li key={index}>{item}</li>                 ))}               </ul>             )}             <div>               <button onClick={handleBioVisibility}>View Bio</button>               {!!location && <p className="location">Location: {location}</p>}             </div>           </>         )}       </div>     </div>   ); }

We will work with the component’s desktop version. You can read more about device-driven code split in React but note that testing mobile components is still pretty straightforward.

Setting up the example project

Not all tests are covered in this article, but we will certainly look at the most interesting ones. If you want to follow along, view this component in the browser, or check all its tests, go ahead and clone the GitHub repo.

## 1. Clone the project: git clone git@github.com:moubi/profile-card.git  ## 2. Navigate to the project folder: cd profile-card  ## 3. Install the dependencies: yarn  ## 4. Start and view the component in the browser: yarn start  ## 5. Run the tests: yarn test

Here’s how the <ProfileCard /> component and UnexpectedJS tests are structured once the project has spun up:

/src   └── /components       ├── /ProfileCard       |   ├── ProfileCard.js       |   ├── ProfileCard.scss       |   └── ProfileCard.test.js       └── /test-utils            └── unexpected-react.js

Component tests

Let’s take a look at some of the component tests. These are located in src/components/ProfileCard/ProfileCard.test.js. Note how each test is organized by the three phases we covered earlier.

  1. Setting up required component props for each test.
beforeEach(() => {   props = {     data: {       name: "Justin Case",       posts: 45,       creationDate: "01.01.2021",     },   }; });

Before each test, a props object with the required <ProfileCard /> props is composed, where props.data contains the minimum info for the component to render.

  1. Render with a default set of props.

This test checks the whole DOM produced by the component when passing name, posts, and creationDate fields.

Here’s what the result produces in the UI:

And here’s the test case for it:

it("should render default", () => {   // "to exhaustively satisfy" ensures all classes/attributes are also matching   expect(     <ProfileCard {...props} />,     "when mounted",     "to exhaustively satisfy",     <div className="ProfileCard">       <div className="avatar">         <h2>Justin Case</h2>         <i className="photo" />         <span>45{" posts"}</span>         <i className="status offline" />       </div>       <div className="details bio">         <h3>Bio</h3>         <p>No bio provided yet</p>         <div>           <button>View Skills</button>           <p className="joined">{"Joined: "}01.01.2021</p>         </div>       </div>     </div>   ); });
  1. Render with status online.

Now we check if the profile renders with the “online” status icon.

And the test case for that:

it("should display online icon", () => {   // Set the isOnline prop   props.data.isOnline = true;    // The minimum to test for is the presence of the .online class   expect(     <ProfileCard {...props} />,     "when mounted",     "queried for first",     ".status",     "to have class",     "online"   ); });
  1. Render with bio text.

<ProfileCard /> accepts any arbitrary string for its bio.

So, let’s write a test case for that:

it("should display online icon", () => {   // Set the isOnline prop   props.data.isOnline = true;    // The minimum to test for is the presence of the .online class   expect(     <ProfileCard {...props} />,     "when mounted",     "queried for first",     ".status",     "to have class",     "online"   ); });
  1. Render “Technologies” view with an empty list.

Clicking on the “View Skills” link should switch to a list of technologies for this user. If no data is passed, then the list should be empty.

Here’s that test case:

it("should display the technologies view", () => {   // Mount <ProfileCard /> and obtain a ref   const component = mount(<ProfileCard {...props} />);    // Simulate a click on the button element ("View Skills" link)   simulate(component, {     type: "click",     target: "button",   });    // Check if the .details element contains the technologies view   expect(     component,     "queried for first",     ".details",     "to exhaustively satisfy",     <div className="details technologies">       <h3>Technologies</h3>       <div>         <button>View Bio</button>       </div>     </div>   ); });
  1. Render a list of technologies.

If a list of technologies is passed, it will display in the UI when clicking on the “View Skills” link.

Yep, another test case:

it("should display list of technologies", () => {   // Set the list of technologies   props.data.technologies = ["JavaScript", "React", "NodeJs"];     // Mount ProfileCard and obtain a ref   const component = mount(<ProfileCard {...props} />);    // Simulate a click on the button element ("View Skills" link)   simulate(component, {     type: "click",     target: "button",   });    // Check if the list of technologies is present and matches prop values   expect(     component,     "queried for first",     ".technologies ul",     "to exhaustively satisfy",     <ul>       <li>JavaScript</li>       <li>React</li>       <li>NodeJs</li>     </ul>   ); });
  1. Render a user location.

That information should render in the DOM only if it was provided as a prop.

The test case:

it("should display location", () => {   // Set the location    props.data.location = "Copenhagen, Denmark";    // Mount <ProfileCard /> and obtain a ref   const component = mount(<ProfileCard {...props} />);      // Simulate a click on the button element ("View Skills" link)   // Location render only as part of the Technologies view   simulate(component, {     type: "click",     target: "button",   });    // Check if the location string matches the prop value   expect(     component,     "queried for first",     ".location",     "to have text",     "Location: Copenhagen, Denmark"   ); });
  1. Calling a callback when switching views.

This test does not compare DOM nodes but does check if a function prop passed to <ProfileCard /> is executed with the correct argument when switching between the Bio and Technologies views.

it("should call onViewChange prop", () => {   // Create a function stub (dummy)   props.data.onViewChange = sinon.stub();      // Mount ProfileCard and obtain a ref   const component = mount(<ProfileCard {...props} />);    // Simulate a click on the button element ("View Skills" link)   simulate(component, {     type: "click",     target: "button",   });    // Check if the stub function prop is called with false value for isBioVisible   // isBioVisible is part of the component's local state   expect(     props.data.onViewChange,     "to have a call exhaustively satisfying",     [false]   ); });

Running all the tests

Now, all of the tests for <ProfileCard /> can be executed with a simple command:

yarn test

Notice that tests are grouped. There are two independent tests and two groups of tests for each of the <ProfileCard /> views—bio and technologies. Grouping makes test suites easier to follow and is a nice way to organize logically-related UI units.

Some final words

Again, this is meant to be a fairly simple example of how to approach React component tests. The essence is to look at components as simple functions that accept props and return a DOM. From that point on, choosing a testing library should be based on the usefulness of the tools it provides for handling component renders and DOM comparisons. UnexpectedJS happens to be very good at that in my experience.

What should be your next steps? Look at the GitHub project and give it a try if you haven’t already! Check all the tests in ProfileCard.test.js and perhaps try to write a few of your own. You can also look at src/test-utils/unexpected-react.js which is a simple helper function exporting features from the third-party testing libraries.

And lastly, here are a few additional resources I’d suggest checking out to dig even deeper into React component testing:


The post React Component Tests for Humans appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Use CSS Variables instead of React Context

Turns out you can use several different libraries to pass color information around components. Or, you could use custom properties, built right into CSS, have no decline in your own developer experience, and deliver a faster experience to your users. Kent proves it here, with demos.

For the record, you could go a step further than Kent has here and not use CSS-in-JS at all, but still be updating CSS custom properties from button clicks in React and managing the state there and such. I’m telling ya, one of the main jobs of a UI component library like React is managing state, and CSS might as well know about that state so you can use it to do any styling you need to do.

Wait, not use CSS-in-JS? Kent:

I’ve never been so productive working with CSS than when I added a real programming language to it.

Extreme side eye, Kent.

We should be calling it CSS-in-React, also, since React is the only major framework that doesn’t have a blessed solution for styling.

Direct Link to ArticlePermalink


The post Use CSS Variables instead of React Context appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Some React Blog Posts I’ve Bookmarked and Read Lately

  • The React Hooks Announcement In Retrospect: 2 Years Later — Ryan Carniato considers hooks to be the most significant turning point in front end in the past five years, but he also says hooks have muddied the waters as well.
  • Mediator Component in React — Robin Wieruch’s article made me think just how un-opinionated React is and how architecturally on-your-own you are. It’s tough to find the right abstractions.
  • No One Ever Got Fired for Choosing React — Jake Lazaroff’s article is a good balance to the above. Sometimes you pick a library like React because it solves problems you’re likely to run into and lets you get to work.
  • A React “if component — I kinda like how JSX does conditional rendering, but hey, a lightweight abstraction like this might just float your boat.
  • State of the React Ecosystem in 2021 — Hooks are big. State management is all over the place, and state machines are growing in popularity. webpack is still the main bundler. Everyone is holding their breath about Suspense… so suspenseful.
  • Blitz.js — “The Fullstack React Framework” — interesting to see a framework built on top of another framework (Next.js).
  • Introducing Zero-Bundle-Size React Server Components — Feels like it will be a big deal, but will shine brightest when frameworks and hosts zero-in on offerings around it. I’m sure Vercel and Netlify are all  👀.

The post Some React Blog Posts I’ve Bookmarked and Read Lately appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , ,
[Top]

3 Approaches to Integrate React with Custom Elements

In my role as a web developer who sits at the intersection of design and code, I am drawn to Web Components because of their portability. It makes sense: custom elements are fully-functional HTML elements that work in all modern browsers, and the shadow DOM encapsulates the right styles with a decent surface area for customization. It’s a really nice fit, especially for larger organizations looking to create consistent user experiences across multiple frameworks, like Angular, Svelte and Vue.

In my experience, however, there is an outlier where many developers believe that custom elements don’t work, specifically those who work with React, which is, arguably, the most popular front-end library out there right now. And it’s true, React does have some definite opportunities for increased compatibility with the web components specifications; however, the idea that React cannot integrate deeply with Web Components is a myth.

In this article, I am going to walk through how to integrate a React application with Web Components to create a (nearly) seamless developer experience. We will look at React best practices its and limitations, then create generic wrappers and custom JSX pragmas in order to more tightly couple our custom elements and today’s most popular framework.

Coloring in the lines

If React is a coloring book — forgive the metaphor, I have two small children who love to color — there are definitely ways to stay within the lines to work with custom elements. To start, we’ll write a very simple custom element that attaches a text input to the shadow DOM and emits an event when the value changes. For the sake of simplicity, we’ll be using LitElement as a base, but you can certainly write your own custom element from scratch if you’d like.

Our super-cool-input element is basically a wrapper with some styles for a plain ol’ <input> element that emits a custom event. It has a reportValue method for letting users know the current value in the most obnoxious way possible. While this element might not be the most useful, the techniques we will illustrate while plugging it into React will be helpful for working with other custom elements.

Approach 1: Use ref

According to React’s documentation for Web Components, “[t]o access the imperative APIs of a Web Component, you will need to use a ref to interact with the DOM node directly.”

This is necessary because React currently doesn’t have a way to listen to native DOM events (preferring, instead, to use it’s own proprietary SyntheticEvent system), nor does it have a way to declaratively access the current DOM element without using a ref.

We will make use of React’s useRef hook to create a reference to the native DOM element we have defined. We will also use React’s useEffect and useState hooks to gain access to the input’s value and render it to our app. We will also use the ref to call our super-cool-input’s reportValue method if the value is ever a variant of the word “rad.”

One thing to take note of in the example above is our React component’s useEffect block.

useEffect(() => {   coolInput.current.addEventListener('custom-input', eventListener);      return () => {     coolInput.current.removeEventListener('custom-input', eventListener);   } });

The useEffect block creates a side effect (adding an event listener not managed by React), so we have to be careful to remove the event listener when the component needs a change so that we don’t have any unintentional memory leaks.

While the above example simply binds an event listener, this is also a technique that can be employed to bind to DOM properties (defined as entries on the DOM object, rather than React props or DOM attributes).

This isn’t too bad. We have our custom element working in React, and we’re able to bind to our custom event, access the value from it, and call our custom element’s methods as well. While this does work, it is verbose and doesn’t really look like React.

Approach 2: Use a wrapper

Our next attempt at using our custom element in our React application is to create a wrapper for the element. Our wrapper is simply a React component that passes down props to our element and creates an API for interfacing with the parts of our element that aren’t typically available in React.

Here, we have moved the complexity into a wrapper component for our custom element. The new CoolInput React component manages creating a ref while adding and removing event listeners for us so that any consuming component can pass props in like any other React component.

function CoolInput(props) {   const ref = useRef();   const { children, onCustomInput, ...rest } = props;      function invokeCallback(event) {     if (onCustomInput) {       onCustomInput(event, ref.current);     }   }      useEffect(() => {     const { current } = ref;     current.addEventListener('custom-input', invokeCallback);     return () => {       current.removeEventListener('custom-input', invokeCallback);     }   });      return <super-cool-input ref={ref} {...rest}>{children}</super-cool-input>; }

On this component, we have created a prop, onCustomInput, that, when present, triggers an event callback from the parent component. Unlike a normal event callback, we chose to add a second argument that passes along the current value of the CoolInput’s internal ref.

Using these same techniques, it is possible to create a generic wrapper for a custom element, such as this reactifyLitElement component from Mathieu Puech. This particular component takes on defining the React component and managing the entire lifecycle.

Approach 3: Use a JSX pragma

One other option is to use a JSX pragma, which is sort of like hijacking React’s JSX parser and adding our own features to the language. In the example below, we import the package jsx-native-events from Skypack. This pragma adds an additional prop type to React elements, and any prop that is prefixed with onEvent adds an event listener to the host.

To invoke a pragma, we need to import it into the file we are using and call it using the /** @jsx <PRAGMA_NAME> */ comment at the top of the file. Your JSX compiler will generally know what to do with this comment (and Babel can be configured to make this global). You might have seen this in libraries like Emotion.

An <input> element with the onEventInput={callback} prop will run the callback function whenever an event with the name 'input' is dispatched. Let’s see how that looks for our super-cool-input.

The code for the pragma is available on GitHub. If you want to bind to native properties instead of React props, you can use react-bind-properties. Let’s take a quick look at that:

import React from 'react'  /**  * Convert a string from camelCase to kebab-case  * @param {string} string - The base string (ostensibly camelCase)  * @return {string} - A kebab-case string  */ const toKebabCase = string => string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$  1-$  2').toLowerCase()  /** @type {Symbol} - Used to save reference to active listeners */ const listeners = Symbol('jsx-native-events/event-listeners')  const eventPattern = /^onEvent/  export default function jsx (type, props, ...children) {   // Make a copy of the props object   const newProps = { ...props }   if (typeof type === 'string') {     newProps.ref = (element) => {       // Merge existing ref prop       if (props && props.ref) {         if (typeof props.ref === 'function') {           props.ref(element)         } else if (typeof props.ref === 'object') {           props.ref.current = element         }       }        if (element) {         if (props) {           const keys = Object.keys(props)           /** Get all keys that have the `onEvent` prefix */           keys             .filter(key => key.match(eventPattern))             .map(key => ({               key,               eventName: toKebabCase(                 key.replace('onEvent', '')               ).replace('-', '')             })           )           .map(({ eventName, key }) => {             /** Add the listeners Map if not present */             if (!element[listeners]) {               element[listeners] = new Map()             }              /** If the listener hasn't be attached, attach it */             if (!element[listeners].has(eventName)) {               element.addEventListener(eventName, props[key])               /** Save a reference to avoid listening to the same value twice */               element[listeners].set(eventName, props[key])             }           })         }       }     }   }      return React.createElement.apply(null, [type, newProps, ...children]) }

Essentially, this code converts any existing props with the onEvent prefix and transforms them to an event name, taking the value passed to that prop (ostensibly a function with the signature (e: Event) => void) and adding it as an event listener on the element instance.

Looking forward

As of the time of this writing, React recently released version 17. The React team had initially planned to release improvements for compatibility with custom elements; unfortunately, those plans seem to have been pushed back to version 18.

Until then it will take a little extra work to use all the features custom elements offer with React. Hopefully, the React team will continue to improve support to bridge the gap between React and the web platform.


The post 3 Approaches to Integrate React with Custom Elements appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]