Tag: React

Easy Dark Mode (and Multiple Color Themes!) in React

I was working on a large React application for a startup, and aside from just wanting some good strategies to keep our styles organized, I wanted to give this whole “dark mode” thing a shot. With the huge ecosystem around React, you might think that there would be a go-to solution for style themes, but a little web searching shows that really isn’t the case.

There are plenty of different options out there, but many of them tie into very specific CSS strategies, like using CSS Modules, some form of CSS-in-JS, etc. I also found tools specific to certain frameworks, like Gatsby, but not a generic React project. What I was looking for was a basic system that’s easy to set up and work with without jumping through a ton of hoops; something fast, something easy to get a whole team of front-end and full-stack developers onboarded with quickly.

The existing solution I liked the best centered around using CSS variables and data attributes, found in this StackOverflow answer. But that also relied on some useRef stuff that felt hack-y. As they say in every infomercial ever, there’s got to be a better way!

Fortunately, there is. By combining that general CSS variable strategy with the beautiful useLocalStorage hook, we have a powerful, easy-to-use theming system. I’m going to walk through setting this thing up and running it, starting from a brand new React app. And if you stick around to the end, I also show you how to integrate it with react-scoped-css, which is what makes this my absolutely preferred way to work with CSS in React.

Project setup

Let’s pick this up at a very good place to start: the beginning.

This guide assumes a basic familiarity with CSS, JavaScript, and React.

First, make sure you have a recent version of Node and npm installed. Then navigate to whatever folder you want your project to live in, run git bash there (or your preferred command line tool), then run:

npx create-react-app easy-react-themes --template typescript

Swap out easy-react-themes with the name of your project, and feel free to leave off the --template typescript if you’d rather work in JavaScript. I happen to like TypeScript but it genuinely makes no difference for this guide, other than files ending in .ts/.tsx vs .js/.jsx.

Now we’ll open up our brand new project in a code editor. I’m using VS Code for this example, and if you are too, then you can run these commands:

cd easy-react-themes code .
Not much to look at yet, but we’ll change that!

Running npm start next starts your development server, and produces this in a new browser window:

And, finally, go ahead and install the use-local-storage package with:

npm i use-local-storage

And that’s it for the initial setup of the project!

Code setup

Open the App.tsx file and get rid of the stuff we don’t need.

We want to go from this…

…to this.

Delete the entire content in App.css:

Woot! Now let’s create our themes! Open up the index.css file and add this to it:

:root {   --background: white;   --text-primary: black;   --text-secondary: royalblue;   --accent: purple; } [data-theme='dark'] {   --background: black;   --text-primary: white;   --text-secondary: grey;   --accent: darkred; }

Here’s what we have so far:

See what we just did there? If you’re unfamiliar with CSS Custom Properties (as also known as CSS variables), they allow us to define a value to be used elsewhere in our stylesheets, with the pattern being --key: value. In this case, we’re only defining a few colors and applying them to the :root element so they can be used be used wherever else we need them across the whole React project.

The second part, starting with [data-theme='dark'], is where things get interesting. HTML (and JSX, which we’re using to create HTML in React) allows us to set completely arbitrary properties for our HTML elements with the data-* attribute. In this case, we are giving the outermost <div> element of our application a data-theme attribute and toggling its value between light and dark. When it’s dark, the CSS[data-theme='dark'] section overrides the variables we defined in the :root, so any styling which relies on those variables is toggled as well.

Let’s put that into practice. Back in App.tsx, let’s give React a way to track the theme state. We’d normally use something like useState for local state, or Redux for global state management, but we also want the user’s theme selection to stick around if they leave our app and come back later. While we could use Redux and redux-persist, that’s way overkill for our needs.

Instead, we’re using the useLocalStorage hook we installed earlier. It gives us a way to store things in local storage, as you might expect, but as a React hook, it maintains stateful knowledge of what it’s doing with localStorage, making our lives easy.

Some of you might be thinking “Oh no, what if the page renders before our JavaScript checks in with localStorage and we get the dreaded “flash of wrong theme?” But you don’t have to worry about that here since our React app is completely rendered client-side; the initial HTML file is basically a skeleton with a with a single <div> that React attaches the app to. All of the final HTML elements are generated by JavaScript after checking localStorage.

So, first, import the hook at the top of App.tsx with:

import useLocalStorage from 'use-local-storage'

Then, inside our App component, we use it with:

const defaultDark = window.matchMedia('(prefers-color-scheme: dark)').matches; const [theme, setTheme] = useLocalStorage('theme', defaultDark ? 'dark' : 'light'); 

This does a few things for us. First, we’re checking if the user has set a theme preference in their browser settings. Then we’re creating a stateful theme variable that is tied to localStorage and the setTheme function to update theme. useLocalStorage adds a key:value pair to localStorage if it doesn’t already exist, which defaults to theme: "light", unless our matchMedia check comes back as true, in which case it’s theme: "dark". That way, we’re gracefully handling both possibilities of keeping the theme settings for a returning user, or respecting their browser settings by default if we’re working with new users.

Next, we add a tiny bit of content to the App component so we have some elements to style, along with a button and function to actually allow us to toggle the theme.

The finished App.tsx file

The secret sauce is on line 14 where we’ve added data-theme={theme} to our top-level <div>. Now, by switching the value of theme, we are choosing whether or not to override the CSS variables in :root with the ones in the data-theme='dark' section of the index.css file.

The last thing we need to do is add some styling that uses those CSS variables we made earlier, and it’ll up and running! Open App.css and drop this CSS in there:

.App {   color: var(--text-primary);   background-color: var(--background);   font-size: large;   font-weight: bold;   padding: 20px;   height: calc(100vh - 40px);   transition: all .5s; } button {   color: var(--text-primary);   background-color: var(--background);   border: 2px var(--text-primary) solid;   float: right;   transition: all .5s; }

Now the background and text for the main <div>, and the background, text, and outline of the <button> rely on the CSS variables. That means when the theme changes, everything that depends on those variables update as well. Also note that we added transition: all .5s to both the App and <button> for a smooth transition between color themes.

Now, head back to the browser that’s running the app, and here’s what we get:

Tada! Let’s add another component just to show how the system works if we’re building out a real app. We’ll add a /components folder in /src, put a /square folder in /components, and add a Square.tsx and square.css, like so:

Let’s import it back into App.tsx, like so:

Here’s what we have now as a result:

And there we go! Obviously, this is a pretty basic case where we’re only using a default (light) theme, and a secondary (dark) theme. But if your application calls for it, this system could be used to implement multiple theme options. Personally, I’m thinking of giving my next project options for light, dark, chocolate, and strawberry—go nuts!

Bonus: Integrating with React Scoped CSS:

Using React Scoped CSS is my favorite way to keep each component’s CSS encapsulated to prevent name collision messiness and unintended style inheritance. My previous go-to for this was CSS Modules, but that has the downside of making the in-browser DOM look like a robot wrote all of the class names… because that’s exactly the case. This lack of human-readability makes debugging far more annoying than it has to be. Enter React Scoped CSS. We get to keep writing CSS (or Sass) exactly the way we have been, and the output looks like a human wrote it.

Seeing as the the React Scoped CSS repo provides full and detailed installation instructions, I’ll merely summarize them here.

First, install and configure Create React App Configuration Override (CRACO) according to their instructions. Craco is a tool that lets us override some of the default webpack configuration that’s bundled into create-react-app (CRA). Normally, if you want to adjust webpack in a CRA project, you first have to “eject” the project, which is an irreversible operation, and makes you fully responsible for all of the dependencies that are normally handled for you. You usually want to avoid ejecting unless you really, really know what you’re doing and have a good reason to go down that road. Instead, CRACO let’s us make some minor adjustments to our webpack config without things getting messy.

Once that’s done, install the React Scoped CSS package:

npm i craco-plugin-scoped-css

(The README instructions use yarn for installation instead of npm, but either is fine.) Now that it’s installed, simply rename the CSS files by adding .scoped before the .css, like so:

app.css -> app.scoped.css

And we need to make sure we’re using a new name when importing that CSS into a component:

import './app.css'; -> import './app.scoped.css';

Now all of the CSS is encapsulated so that it only applies to the components they’re imported into. It works by using data-* properties, much like our theme system, so when a scoped CSS file is imported into a component, all of that component’s elements are labeled with a property, like data-v-46ef2374, and the styles from that file are wrapped so that they only apply to elements with that exact data property.

That’s all wonderful, but the little trick to making that work with this theming system is that we explicitly don’t want the CSS variables encapsulated; we want them applied to the whole project. So, we simply don’t change index.css to have scoped in it… in other words, we can leave that CSS file alone. That’s it! Now we have a powerful theming system working in harmony with scoped CSS— we’re living the dream!

Thank you so much taking a read through this guide, and if it helped you build something awesome, I would love to know about it!


The post Easy Dark Mode (and Multiple Color Themes!) in React appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,

React Suspense: Lessons Learned While Loading Data

Suspense is React’s forthcoming feature that helps coordinate asynchronous actions—like data loading—allowing you to easily prevent inconsistent state in your UI. I’ll provide a better explanation of what exactly that means, along with a quick introduction of Suspense, and then go over a somewhat realistic use case, and cover some lessons learned.

The features I’m covering are still in the alpha stage, and should by no means be used in production. This post is for folks who want to take a sneak peek at what’s coming, and see what the future looks like.

A Suspense primer

One of the more challenging parts of application development is coordinating application state and how data loads. It’s common for a state change to trigger new data loads in multiple locations. Typically, each piece of data would have its own loading UI (like a “spinner”), roughly where that data lives in the application. The asynchronous nature of data loading means each of these requests can be returned in any order. As a result, not only will your app have a bunch of different spinners popping in and out, but worse, your application might display inconsistent data. If two out of three of your data loads have completed, you’ll have a loading spinner sitting on top of that third location, still displaying the old, now outdated data.

I know that was a lot. If you find any of that baffling, you might be interested in a prior post I wrote about Suspense. That goes into much more detail on what Suspense is and what it accomplishes. Just note that a few minor pieces of it are now outdated, namely, the useTransition hook no longer takes a timeoutMs value, and waits as long as needed instead.

Now let’s do a quick walkthrough of the details, then get into a specific use case, which has a few lurking gotchas.

How does Suspense work?

Fortunately, the React team was smart enough to not limit these efforts to just loading data. Suspense works via low-level primitives, which you can apply to just about anything. Let’s take a quick look at these primitives.

First up is the <Suspense> boundary, which takes a fallback prop:

<Suspense fallback={<Fallback />}>

Whenever any child under this component suspends, it renders the fallback. No matter how many children are suspending, for whatever reason, the fallback is what shows. This is one way React ensures a consistent UI—it won’t render anything, until everything is ready.

But what about after things have rendered, initially, and now the user changes state, and loads new data. We certainly don’t want our existing UI to vanish and display our fallback; that would be a poor UX. Instead, we probably want to show one loading spinner, until all data are ready, and then show the new UI.

The useTransition hook accomplishes this. This hook returns a function and a boolean value. We call the function and wrap our state changes. Now things get interesting. React attempts to apply our state change. If anything suspends, React sets that boolean to true, then waits for the suspension to end. When it does, it’ll try to apply the state change again. Maybe it’ll succeed this time, or maybe something else suspends instead. Whatever the case, the boolean flag stays true until everything is ready, and then, and only then, does the state change complete and get reflected in the UI.

Lastly, how do we suspend? We suspend by throwing a promise. If data is requested, and we need to fetch, then we fetch—and throw a promise that’s tied to that fetch. The suspension mechanism being at a low level like this means we can use it with anything. The React.lazy utility for lazy loading components works with Suspense already, and I’ve previously written about using Suspense to wait until images are loaded before displaying a UI in order to prevent content from shifting.

Don’t worry, we’ll get into all this.

What we’re building

We’ll build something slightly different than the examples of many other posts like this. Remember, Suspense is still in alpha, so your favorite data loading utility probably doesn’t have Suspense support just yet. But that doesn’t mean we can’t fake a few things and get an idea of how Suspense works.

Let’s build an infinite loading list that displays some data, combined with some Suspense-based preloaded images. We’ll display our data, along with a button to load more. As data renders, we’ll preload the associated image, and Suspend until it’s ready.

This use case is based on actual work I’ve done on my side project (again, don’t use Suspense in production—but side projects are fair game). I was using my own GraphQL client, and this post is motivated by some of the difficulties I ran into. We’ll just fake the data loading in order to keep things simple and focus on Suspense itself, rather than any individual data loading utility.

Let’s build!

Here’s the sandbox for our initial attempt. We’re going to use it to walk through everything, so don’t feel pressured to understand all the code right now.

Our root App component renders a Suspense boundary like this:

<Suspense fallback={<Fallback />}>

Whenever anything suspends (unless the state change happened in a useTransition call), the fallback is what renders. To make things easier to follow, I made this Fallback component turn the entire UI pink, that way it’s tough to miss; our goal is to understand Suspense, not to build a quality UI.

We’re loading the current chunk of data inside of our DataList component:

const newData = useQuery(param);

Our useQuery hook is hardcoded to return fake data, including a timeout that simulates a network request. It handles caching the results and throws a promise if the data is not yet cached.

We’re keeping (at least for now) state in the master list of data we’re displaying:

const [data, setData] = useState([]);

As new data comes in from our hook, we append it to our master list:

useEffect(() => {   setData((d) => d.concat(newData)); }, [newData]);

Lastly, when the user wants more data, they click the button, which calls this:

function loadMore() {   startTransition(() => {     setParam((x) => x + 1);   }); }

Finally, note that I’m using a SuspenseImg component to handle preloading the image I’m displaying with each piece of data. There are only five random images being displayed, but I’m adding a query string to ensure a fresh load for each new piece of data we encounter.

Recap

To summarize where we are at this point, we have a hook that loads the current data. The hook obeys Suspense mechanics, and throws a promise while loading is happening. Whenever that data changes, the running total list of items is updated and appended with the new items. This happens in useEffect. Each item renders an image, and we use a SuspenseImg component to preload the image, and suspend until it’s ready. If you’re curious how some of that code works, check out my prior post on preloading images with Suspense.

Let’s test

This would be a pretty boring blog post if everything worked, and don’t worry, it doesn’t. Notice how, on the initial load, the pink fallback screen shows and then quickly hides, but then is redisplayed.

When we click the button that’s loads more data, we see the inline loading indicator (controlled by the useTransition hook) flip to true. Then we see it flip to false, before our original pink fallback shows. We were expecting to never see that pink screen again after the initial load; the inline loading indicator was supposed to show until everything was ready. What’s going on?

The problem

It’s been hiding right here in plain sight the entire time:

useEffect(() => {   setData((d) => d.concat(newData)); }, [newData]);

useEffect runs when a state change is complete, i.e., a state change has finished suspending, and has been applied to the DOM. That part, “has finished suspending,” is key here. We can set state in here if we’d like, but if that state change suspends, again, that is a brand new suspension. That’s why we saw the pink flash on initial load, as well subsequent loads when the data finished loading. In both cases, the data loading was finished, and then we set state in an effect which caused that new data to actually render, and suspend again, because of the image preloads.

So, how do we fix this? On one level, the solution is simple: stop setting state in the effect. But that’s easier said than done. How do we update our running list of entries to append new results as they come in, without using an effect. You might think we could track things with a ref.

Unfortunately, Suspense comes with some new rules about refs, namely, we can’t set refs inside of a render. If you’re wondering why, remember that Suspense is all about React attempting to run a render, seeing that promise get thrown, and then discarding that render midway through. If we mutated a ref before that render was cancelled and discarded, the ref would still have that changed, but invalid value. The render function needs to be pure, without side effects. This has always been a rule with React, but it matters more now.

Re-thinking our data loading

Here’s the solution, which we’ll go over, piece by piece.

First, instead of storing our master list of data in state, let’s do something different: let’s store a list of pages we’re viewing. We can store the most recent page in a ref (we won’t write to it in render, though), and we’ll store an array of all currently-loaded pages in state.

const currentPage = useRef(0); const [pages, setPages] = useState([currentPage.current]);

In order to load more data, we’ll update accordingly:

function loadMore() {   startTransition(() => {     currentPage.current = currentPage.current + 1;     setPages((pages) => pages.concat(currentPage.current));   }); }

The tricky part, however, is turning those page numbers into actual data. What we certainly cannot do is loop over those pages and call our useQuery hook; hooks cannot be called in a loop. What we need is a new, non-hook-based data API. Based on a very unofficial convention I’ve seen in past Suspense demos, I’ll name this method read(). It is not going to be a hook. It returns the requested data if it’s cached, or throws a promise otherwise. For our fake data loading hook, no real changes were necessary; I simple copy-and-pasted the hook, then renamed it. But for an actual data loading utility library, authors will likely need to do some work to expose both options as part of their public API. In my GraphQL client referenced earlier, there is indeed both a useSuspenseQuery hook, and also a read() method on the client object.

With this new read() method in place, the final piece of our code is trivial:

const data = pages.flatMap((page) => read(page));

We’re taking each page, and requesting the corresponding data with our read() method. If any of the pages are uncached (which really should only be the last page in the list) then a promise is thrown, and React suspends for us. When the promise resolves, React attempts the prior state change again, and this code runs again.

Don’t let the flatMap call confuse you. That does the exact same thing as map except it takes each result in the new array and, if it itself is an array, “flattens” it.

The result

With these changes in place, everything works as we expected it to when we started. Our pink loading screen shows once on their initial load, then, on subsequent loads, the inline loading state shows until everything is ready.

Parting thoughts

Suspense is an exciting update that’s coming to React. It’s still in the alpha stages, so don’t try to use it anywhere that matters. But if you’re the kind of developer who enjoys taking a sneak peek at upcoming things, then I hope this post provided you some good context and info that’s useful when this releases.


The post React Suspense: Lessons Learned While Loading Data appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Introducing Svelte, and Comparing Svelte with React and Vue

Josh Collingsworth is clearly a big fan of Svelte, so while this is a fun and useful comparison article, it’s here to crown Svelte the winner all the way through.

A few things I find compelling:

One of the things I like most about Svelte is its HTML-first philosophy. With few exceptions, Svelte code is entirely browser-readable HTML and JavaScript. In fact, technically, you could call Svelte code as a small superset of HTML.

And:

Svelte is reactive by default. This means when a variable is reassigned, every place it’s used or referenced also updates automatically. (React and Vue both require you to explicitly initialize reactive variables.)

I do find the component format nice to look at, like how you just write HTML. You don’t even need a <template> around it, or to return anything. I imagine Astro took inspiration from this in how you can also just chuck a <style> tag in there and scope styles if you want. But I think I prefer how the “fenced” JavaScript at the top only runs during the build by default.


P.S. I really like Josh’s header/footer random square motif so I tried to reverse engineer it:

To Shared LinkPermalink on CSS-Tricks


The post Introducing Svelte, and Comparing Svelte with React and Vue appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

A Themeable React Data Grid With Great UX-Focused Features

(This is a sponsored post.)

KendoReact can save you boatloads of time because it offers pre-built componentry you can use in your app right away. They look nice, but more importantly, they are easily themeable, so they look however you need them to look. And I’d say the looks aren’t even the important part. There are lots of component libraries out there that focus on the visuals. These components tackle the hardest interactivity problems in UI/UX, and do it with grace, speed, and accessibility in mind.

Let’s take a look at their React Data Grid component.

The ol’ <table> element is the right tool for the job for data grids, but a table doesn’t offer most of the features that make for a good data browsing experience. If we use the KendoReact <Grid /> component (and friends), we get an absolute ton of extra features, any one of which is non-trivial to pull off nicely, and all together make for an extremely compelling solution. Let’s go through a list of what you get.

Sortable Columns

You’ll surely pick a default ordering for your data, but if any given row of data has things like ID’s, dates, or names, it’s perfectly likely that a user would want to sort the column by that data. Perhaps they want to view the oldest orders, or the orders of the highest total value. HTML does not help with ordering in tables, so this is table stakes (get it?!) for a JavaScript library for data grids, and it’s perfectly handled here.

Pagination and Limits

When you have any more than, say, a few dozen rows of data, it’s common that you want to paginate it. That way users don’t have to scroll as much, and equally importantly, it keeps the page fast by not making the DOM too enormous. One of the problems with pagination though is it makes things like sorting harder! You can’t just sort the 20 rows you can see, it is expected that the entire data set gets sorted. Of course that’s handled in KendoReact’s Data Grid component.

Or, if pagination isn’t your thing, the data grid offers virtualized scrolling — in both the column and row directions. That’s a nice touch as the data loads quickly for smooth, natural scrolling.

Expandable Rows

A data grid might have a bunch of data visible across the row itself, but there might be even more data that a user might want to dig out of an entry once they find it. Perhaps it is data that doesn’t need to be cross-referenced in the same way column data is. This can be tricky to pull off, because of the way table cells are laid out. The data is still associated with a single row, but you often need more room than the width of one cell offers. With the KendoReact Data Grid component, you can pass in a detail prop with an arbitrary React component to show when a row is expanded. Super flexible!

Notice how the expanded details can have their own <Grid /> inside!

Responsive Design

Perhaps the most notoriously difficult thing to pull off with <table> designs is how to display them on small screens. Zooming out isn’t very good UX, nor is collapsing the table into something non-table-like. The thing about data grids is that they are all different, and you’ll know data is most important to your users best. The KendoReact Data Grid component helps with this by making your data grid scrollable/swipeable, and also being able to lock columns to make sure they continue to be easy to find and cross-reference.

Filtering Data

This is perhaps my favorite feature just because of how UX-focused it is. Imagine you’re looking at a big data grid of orders, and you’re like “Let me see all orders from White Clover Markets.” With a filtering feature, perhaps you quickly type “clover” into the filter input, and viola, all those orders are right there. That’s extra tricky stuff when you’re also supporting ordering and pagination — so it’s great all these features work together.

Grouping Data

Now this feature actually blows my mind 🤯 a little bit. Filtering and sorting are both very useful, but in some cases, they leave a little bit to be desired. For example, it’s easy to filter too far too quickly, leaving the data you are looking at very limited. And with sorting, you might be trying to look at a subset of data as well, but it’s up to your brain to figure out where that data begins and ends. With grouping, you can tell the data grid to strongly group together things that are the most important to you, but then still leverage filtering and sorting on top of that. It instantly makes your data exploration easier and more useful.

Localization

This is where you can really tell KendoReact went full monty. It would be highly unfortunate to pick some kind of component library and then realize that you need localization and realize it wasn’t made to be a first-class citizen. You avoid all that with KendoReact, which you can see in this Data Grid component. In the demo, you can flip out English for Spanish with a simple dropdown and see all the dates localized. You pull off any sort of translation and localization with the <LocalizationProvider> and <IntlProvider>, both comfortable React concepts.

Exporting to PDF or Excel

Here’s a live demo of this:

C’mon now! That’s very cool.

That’s not all…

Go check out the docs for the React Data Grid. There are a bunch more features we didn’t even get to here (row pinning! cell editing!). And here’s something to ease your mind: this component, and all the KendoReact components, are keyboard friendly and meet Section 508 accessibility standards. That is no small feat. When components are this complex and involve this much interactivity, getting the accessibility right is tough. So not only are you getting good-looking components that work everywhere, you’re getting richly interactive components that deliver UX beyond what you might even think of, and it’s all done fast and accessiblty. That’s pretty unreal, really.


The post A Themeable React Data Grid With Great UX-Focused Features appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

Links on React and JavaScript II


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

CSS-Tricks

, ,
[Top]

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

, , , ,
[Top]

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]