Tag: User

Reliably Send an HTTP Request as a User Leaves a Page

On several occasions, I’ve needed to send off an HTTP request with some data to log when a user does something like navigate to a different page or submit a form. Consider this contrived example of sending some information to an external service when a link is clicked:

<a href="/some-other-page" id="link">Go to Page</a>  <script> document.getElementById('link').addEventListener('click', (e) => {   fetch("/log", {     method: "POST",     headers: {       "Content-Type": "application/json"     },      body: JSON.stringify({       some: "data"     })   }); }); </script>

There’s nothing terribly complicated going on here. The link is permitted to behave as it normally would (I’m not using e.preventDefault()), but before that behavior occurs, a POST request is triggered on click. There’s no need to wait for any sort of response. I just want it to be sent to whatever service I’m hitting.

On first glance, you might expect the dispatch of that request to be synchronous, after which we’d continue navigating away from the page while some other server successfully handles that request. But as it turns out, that’s not what always happens.

Browsers don’t guarantee to preserve open HTTP requests

When something occurs to terminate a page in the browser, there’s no guarantee that an in-process HTTP request will be successful (see more about the “terminated” and other states of a page’s lifecycle). The reliability of those requests may depend on several things — network connection, application performance, and even the configuration of the external service itself.

As a result, sending data at those moments can be anything but reliable, which presents a potentially significant problem if you’re relying on those logs to make data-sensitive business decisions.

To help illustrate this unreliability, I set up a small Express application with a page using the code included above. When the link is clicked, the browser navigates to /other, but before that happens, a POST request is fired off.

While everything happens, I have the browser’s Network tab open, and I’m using a “Slow 3G” connection speed. Once the page loads and I’ve cleared the log out, things look pretty quiet:

Viewing HTTP request in the network tab

But as soon as the link is clicked, things go awry. When navigation occurs, the request is cancelled.

Viewing HTTP request fail in the network tab

And that leaves us with little confidence that the external service was actually able process the request. Just to verify this behavior, it also occurs when we navigate programmatically with window.location:

document.getElementById('link').addEventListener('click', (e) => { + e.preventDefault();    // Request is queued, but cancelled as soon as navigation occurs.    fetch("/log", {     method: "POST",     headers: {       "Content-Type": "application/json"     },      body: JSON.stringify({       some: 'data'     }),   });  + window.location = e.target.href; });

Regardless of how or when navigation occurs and the active page is terminated, those unfinished requests are at risk for being abandoned.

But why are they cancelled?

The root of the issue is that, by default, XHR requests (via fetch or XMLHttpRequest) are asynchronous and non-blocking. As soon as the request is queued, the actual work of the request is handed off to a browser-level API behind the scenes.

As it relates to performance, this is good — you don’t want requests hogging the main thread. But it also means there’s a risk of them being deserted when a page enters into that “terminated” state, leaving no guarantee that any of that behind-the-scenes work reaches completion. Here’s how Google summarizes that specific lifecycle state:

A page is in the terminated state once it has started being unloaded and cleared from memory by the browser. No new tasks can start in this state, and in-progress tasks may be killed if they run too long.

In short, the browser is designed with the assumption that when a page is dismissed, there’s no need to continue to process any background processes queued by it.

So, what are our options?

Perhaps the most obvious approach to avoid this problem is, as much as possible, to delay the user action until the request returns a response. In the past, this has been done the wrong way by use of the synchronous flag supported within XMLHttpRequest. But using it completely blocks the main thread, causing a host of performance issues — I’ve written about some of this in the past — so the idea shouldn’t even be entertained. In fact, it’s on its way out of the platform (Chrome v80+ has already removed it).

Instead, if you’re going to take this type of approach, it’s better to wait for a Promise to resolve as a response is returned. After it’s back, you can safely perform the behavior. Using our snippet from earlier, that might look something like this:

document.getElementById('link').addEventListener('click', async (e) => {   e.preventDefault();    // Wait for response to come back...   await fetch("/log", {     method: "POST",     headers: {       "Content-Type": "application/json"     },      body: JSON.stringify({       some: 'data'     }),   });    // ...and THEN navigate away.    window.location = e.target.href; });

That gets the job done, but there are some non-trivial drawbacks.

First, it compromises the user’s experience by delaying the desired behavior from occurring. Collecting analytics data certainly benefits the business (and hopefully future users), but it’s less than ideal to make your present users to pay the cost to realize those benefits. Not to mention, as an external dependency, any latency or other performance issues within the service itself will be surfaced to the user. If timeouts from your analytics service cause a customer from completing a high-value action, everyone loses.

Second, this approach isn’t as reliable as it initially sounds, since some termination behaviors can’t be programmatically delayed. For example, e.preventDefault() is useless in delaying someone from closing a browser tab. So, at best, it’ll cover collecting data for some user actions, but not enough to be able to trust it comprehensively.

Instructing the browser to preserve outstanding requests

Thankfully, there are options to preserve outstanding HTTP requests that are built into the vast majority of browsers, and that don’t require user experience to be compromised.

Using Fetch’s keepalive flag

If the keepalive flag is set to true when using fetch(), the corresponding request will remain open, even if the page that initiated that request is terminated. Using our initial example, that’d make for an implementation that looks like this:

<a href="/some-other-page" id="link">Go to Page</a>  <script>   document.getElementById('link').addEventListener('click', (e) => {     fetch("/log", {       method: "POST",       headers: {         "Content-Type": "application/json"       },        body: JSON.stringify({         some: "data"       }),        keepalive: true     });   }); </script>

When that link is clicked and page navigation occurs, no request cancellation occurs:

Viewing HTTP request succeed in the network tab

Instead, we’re left with an (unknown) status, simply because the active page never waited around to receive any sort of response.

A one-liner like this an easy fix, especially when it’s part of a commonly used browser API. But if you’re looking for a more focused option with a simpler interface, there’s another way with virtually the same browser support.

Using Navigator.sendBeacon()

The Navigator.sendBeacon()function is specifically intended for sending one-way requests (beacons). A basic implementation looks like this, sending a POST with stringified JSON and a “text/plain” Content-Type:

navigator.sendBeacon('/log', JSON.stringify({   some: "data" }));

But this API doesn’t permit you to send custom headers. So, in order for us to send our data as “application/json”, we’ll need to make a small tweak and use a Blob:

<a href="/some-other-page" id="link">Go to Page</a>  <script>   document.getElementById('link').addEventListener('click', (e) => {     const blob = new Blob([JSON.stringify({ some: "data" })], { type: 'application/json; charset=UTF-8' });     navigator.sendBeacon('/log', blob));   }); </script>

In the end, we get the same result — a request that’s allowed to complete even after page navigation. But there’s something more going on that may give it an edge over fetch(): beacons are sent with a low priority.

To demonstrate, here’s what’s shown in the Network tab when both fetch() with keepalive and sendBeacon() are used at the same time:

Viewing HTTP request in the network tab

By default, fetch() gets a “High” priority, while the beacon (noted as the “ping” type above) have the “Lowest” priority. For requests that aren’t critical to the functionality of the page, this is a good thing. Taken straight from the Beacon specification:

This specification defines an interface that […] minimizes resource contention with other time-critical operations, while ensuring that such requests are still processed and delivered to destination.

Put another way, sendBeacon() ensures its requests stay out of the way of those that really matter for your application and your user’s experience.

An honorable mention for the ping attribute

It’s worth mentioning that a growing number of browsers support the ping attribute. When attached to links, it’ll fire off a small POST request:

<a href="http://localhost:3000/other" ping="http://localhost:3000/log">   Go to Other Page </a>

And those requests headers will contain the page on which the link was clicked (ping-from), as well as the href value of that link (ping-to):

headers: {   'ping-from': 'http://localhost:3000/',   'ping-to': 'http://localhost:3000/other'   'content-type': 'text/ping'   // ...other headers },

It’s technically similar to sending a beacon, but has a few notable limitations:

  1. It’s strictly limited for use on links, which makes it a non-starter if you need to track data associated with other interactions, like button clicks or form submissions.
  2. Browser support is good, but not great. At the time of this writing, Firefox specifically doesn’t have it enabled by default.
  3. You’re unable to send any custom data along with the request. As mentioned, the most you’ll get is a couple of ping-* headers, along with whatever other headers are along for the ride.

All things considered, ping is a good tool if you’re fine with sending simple requests and don’t want to write any custom JavaScript. But if you’re needing to send anything of more substance, it might not be the best thing to reach for.

So, which one should I reach for?

There are definitely tradeoffs to using either fetch with keepalive or sendBeacon() to send your last-second requests. To help discern which is the most appropriate for different circumstances, here are some things to consider:

You might go with fetch() + keepalive if:

  • You need to easily pass custom headers with the request.
  • You want to make a GET request to a service, rather than a POST.
  • You’re supporting older browsers (like IE) and already have a fetch polyfill being loaded.

But sendBeacon() might be a better choice if:

  • You’re making simple service requests that don’t need much customization.
  • You prefer the cleaner, more elegant API.
  • You want to guarantee that your requests don’t compete with other high-priority requests being sent in the application.

Avoid repeating my mistakes

There’s a reason I chose to do a deep dive into the nature of how browsers handle in-process requests as a page is terminated. A while back, my team saw a sudden change in the frequency of a particular type of analytics log after we began firing the request just as a form was being submitted. The change was abrupt and significant — a ~30% drop from what we had been seeing historically.

Digging into the reasons this problem arose, as well as the tools that are available to avoid it again, saved the day. So, if anything, I’m hoping that understanding the nuances of these challenges help someone avoid some of the pain we ran into. Happy logging!


Reliably Send an HTTP Request as a User Leaves a Page originally published on CSS-Tricks. You should get the newsletter.

CSS-Tricks

, , , , , ,

User Registration and Auth Using Firebase and React

The ability to identify users is vital for maintaining the security of any applications. Equally important is the code that’s written to manage user identities, particularly when it comes to avoiding loopholes for unauthorized access to data held by an application. Writing authentication code without a framework or libraries available can take a ton of time to do right — not to mention the ongoing maintainance of that custom code.

This is where Firebase comes to the rescue. Its ready-to-use and intuitive methods make setting up effective user identity management on a site happen in no time. This tutorial will work us through on how to do that: implementing user registration, verification, and authentication.

Firebase v9 SDK introduces a new modular API surface, resulting in a change to several of its services, one of which is Firebase Authentication. This tutorial is current to the changes in v9.

To follow along with this tutorial, you should be familiar with React, React hooks, and Firebase version 8. You should also have a Google account and Node installed on your machine.

Table of Contents

Setting up Firebase

Before we start using Firebase for our registration and authentication requirements, we have to first set up our Firebase project and also the authentication method we’re using.

To add a project, make sure you are logged into your Google account, then navigate to the Firebase console and click on Add project. From there, give the project a name (I’m using “Firebase-user-reg-auth”) and we should be all set to continue.

You may be prompted to enable Google Analytics at some point. There’s no need for it for this tutorial, so feel free to skip that step.

Firebase has various authentication methods for both mobile and web, but before we start using any of them, we have to first enable it on the Firebase Authentication page. From the sidebar menu, click on the Authentication icon, then, on the next page, click on Get started.

We are going to use Email/Password authentication. Click on it and we will be prompted with a screen to enable it, which is exactly what we want to do.

Cloning and setting up the starter repo

I have already created a simple template we can use for this tutorial so that we can focus specifically on learning how to implement the functionalities. So what we need to do now is clone the GitHub repo.

Fire up your terminal. Here’s what we can run from the command line:

git clone -b starter https://github.com/Tammibriggs/Firebase_user_auth.git  cd Firebase_user_auth  npm install

I have also included Firebase version 9 in the dependency object of the package.json file. So, by running the npm install command, Firebase v9 — along with all other dependencies — will be installed.

With done that, let’s start the app with npm start!

Integrating Firebase into our React app

To integrate Firebase, we need to first get the web configuration object and then use it to initialize Firebase in our React app. Go over to the Firebase project page and we will see a set of options as icons like this:

Click on the web (</>) icon to configure our Firebase project for the web, and we will see a page like this:

Enter firebase-user-auth as the name of the web app. After that, click on the Register app button, which takes us to the next step where our firebaseConfig object is provided.

Copy the config to the clipboard as we will need it later on to initialize Firebase. Then click on the Continue to console button to complete the process.

Now, let’s initialize Firebase and Firebase Authentication so that we can start using them in our app. In the src directory of our React app, create a firebase.js file and add the following imports:

// src/firebase.js import { initializeApp } from 'firebase/app' import {getAuth} from 'firebase/auth'

Now, paste the config we copied earlier after the imports and add the following lines of code to initialize Firebase and Firebase Authentication.

// src/firebase.js const app = initializeApp(firebaseConfig) const auth = getAuth(app)  export {auth}

Our firebase.js file should now look something like this:

// src.firebase.js import { initializeApp } from "firebase/app" import { getAuth } from "firebase/auth"  const firebaseConfig = {   apiKey: "API_KEY",   authDomain: "AUTH_DOMAIN",   projectId: "PROJECT_ID",   storageBucket: "STORAGE_BUCKET",   messagingSenderId: "MESSAGING_SENDER_ID",   appId: "APP_ID" }  // Initialize Firebase and Firebase Authentication const app = initializeApp(firebaseConfig) const auth = getAuth(app) export {auth}

Next up, we’re going to cover how to use the ready-to-use functions provided by Firebase to add registration, email verification, and login functionality to the template we cloned.

Creating User Registration functionality

In Firebase version 9, we can build functionality for user registration with the createUserWithEmailAndPassword function. This function takes three arguments:

  • auth instance/service
  • email
  • password

Services are always passed as the first arguments in version 9. In our case, it’s the auth service.

To create this functionality, we will be working with the Register.js file in the src directory of our cloned template. What I did in this file is create three form fields — email, password, and confirm password — and input is controlled by the state. Now, let’s get to business.

Let’s start by adding a function that validates the password and confirm password inputs, checking if they are not empty and are the same: Add the following lines of code after the states in the Register component:

// src/Register.js // ...  const validatePassword = () => {   let isValid = true   if (password !== '' && confirmPassword !== ''){     if (password !== confirmPassword) {       isValid = false       setError('Passwords does not match')     }   }   return isValid }  // ...

In the above function, we return an isValid variable which can return either true or false based on the validity of the passwords. Later on, we will use the value of this variable to create a condition where the Firebase function responsible for registering users will only be invoked if isValid is true.

To create the registration functionality, let’s start by making the necessary imports to the Register.js file:

// src/Register.js import {auth} from './firebase' import {createUserWithEmailAndPassword} from 'firebase/auth'

Now, add the following lines of code after the validatePassword password function:

// src/Register.js // ...  const register = e => {   e.preventDefault()   setError('')   if(validatePassword()) {     // Create a new user with email and password using firebase       createUserWithEmailAndPassword(auth, email, password)       .then((res) => {           console.log(res.user)         })       .catch(err => setError(err.message))   }   setEmail('')   setPassword('')   setConfirmPassword('') }  // ...

In the above function, we set a condition to call the createUserWithEmailAndPassword function only when the value returning from validatePassword is true.

For this to start working, let’s call the register function when the form is submitted. We can do this by adding an onSubmit event to the form. Modify the opening tag of the registration_form to look like this:

// src/Register.js <form onSubmit={register} name='registration_form'>

With this, we can now register a new user on our site. To test this by going over to http://localhost:3000/register in the browser, filling in the form, then clicking on the Register button.

Showing a user registration form with fields to enter an email. a password, and password confirmation. A gray button labeled Register is below the three stacked fields.

After clicking the Register button, if we open the browser’s console we will see details of the newly registered user.

Managing User State with React Context API

Context API is a way to share data with components at any level of the React component tree without having to pass it down as props. Since a user might be required by a different component in the tree, using the Context API is great for managing the user state.

Before we start using the Context API, there are a few things we need to set up:

  • Create a context object using the createContext() method
  • Pass the components we want to share the user state with as children of Context.Provider
  • Pass the value we want the children/consuming component to access as props to Context.Provider

Let’s get to it. In the src directory, create an AuthContext.js file and add the following lines of code to it:

// src/AuthContext.js import React, {useContext} from 'react'  const AuthContext = React.createContext()  export function AuthProvider({children, value}) {   return (     <AuthContext.Provider value={value}>       {children}     </AuthContext.Provider>   ) }  export function useAuthValue(){   return useContext(AuthContext) }

In the above code, we created a context called AuthContext along with that we also created two other functions that will allow us to easily use the Context API which is AuthProvider and useAuthValue.

The AuthProvider function allows us to share the value of the user’s state to all the children of AuthContext.Provider while useAuthValue allows us to easily access the value passed to AuthContext.Provider.

Now, to provide the children and value props to AuthProvider, modify the App.js file to look something like this:

// src/App.js // ... import {useState} from 'react' import {AuthProvider} from './AuthContext'  function App() {   const [currentUser, setCurrentUser] = useState(null)    return (     <Router>       <AuthProvider value={{currentUser}}>         <Switch>          ...         </Switch>       </AuthProvider>     </Router>   ); }  export default App;

Here, we’re wrapping AuthProvider around the components rendered by App. This way, the currentUser value supplied to AuthProvider will be available for use by all the components in our app except the App component.

That’s it as far as setting up the Context API! To use it, we have to import the useAuthValue function and invoke it in any of the child components of AuthProvider, like Login. The code looks something like this:

import { useAuthValue } from "./AuthContext"  function childOfAuthProvider(){   const {currentUser} = useAuthValue()   console.log(currentUser)    return ... }

Right now, currentUser will always be null because we are not setting its value to anything. To set its value, we need to first get the current user from Firebase which can be done either by using the auth instance that was initialized in our firebase.js file (auth.currentUser), or the onAuthStateChanged function, which actually happens to be the recommended way to get the current user. That way, we ensure that the Auth object isn’t in an intermediate state — such as initialization — when we get the current user.

In the App.js file, add a useEffect import along with useState and also add the following imports:

// src/App.js import {useState, useEffect} from 'react' import {auth} from './firebase' import {onAuthStateChanged} from 'firebase/auth'

Now add the following line of code after the currentUser state in the App component:

// src/App.js // ...  useEffect(() => {   onAuthStateChanged(auth, (user) => {     setCurrentUser(user)    }) }, [])  // ...

In the above code, we are getting the current user and setting it in the state when the component renders. Now when we register a user the currentUser state will be set with an object containing the user’s info.

Send a verification email to a registered user

Once a user is registered, we want them to verify their email address before being able to access the homepage of our site. We can use the sendEmailVerification function for this. It takes only one argument which is the object of the currently registered user. When invoked, Firebase sends an email to the registered user’s email address with a link where the user can verify their email.

Let’s head over to the Register.js file and modify the Link and createUserWithEmailAndPassword import to look like this:

// src/Register.js import {useHistory, Link} from 'react-router-dom' import {createUserWithEmailAndPassword, sendEmailVerification} from 'firebase/auth'

In the above code, we have also imported the useHistory hook. This will help us access and manipulate the browser’s history which, in short, means we can use it to switch between pages in our app. But before we can use it we need to call it, so let’s add the following line of code after the error state:

// src/Register.js // ... const history = useHistory()  // ...

Now, modify the .then method of the createUserWithEmailAndPassword function to look like this:

// src/Register.js // ... .then(() => {   sendEmailVerification(auth.currentUser)   .then(() => {     history.push('/verify-email')   }).catch((err) => alert(err.message)) }) // ...

What’s happening here is that when a user registers a valid email address, they will be sent a verification email, then taken to the verify-email page.

There are several things we need to do on this page:

  • Display the user’s email after the part that says “A verification email has been sent to:”
  • Make the Resend Email button work
  • Create functionality for disabling the Resend Email button for 60 seconds after it is clicked
  • Take the user to their profile page once the email has been verified

We will start by displaying the registered user’s email. This calls for the use of the AuthContext we created earlier. In the VerifyEmail.js file, add the following import:

// src/VerifyEmail.js import {useAuthValue} from './AuthContext'

Then, add the following code before the return statement in the VerifyEmail component:

// src/VerifyEmail.js const {currentUser} = useAuthValue()

Now, to display the email, add the following code after the <br/> tag in the return statement.

// src/VerifyEmail.js // ... <span>{currentUser?.email}</span> // ...

In the above code, we are using optional chaining to get the user’s email so that when the email is null our code will throw no errors.

Now, when we refresh the verify-email page, we should see the email of the registered user.

Let’s move to the next thing which is making the Resend Email button work. First, let’s make the necessary imports. Add the following imports to the VerifyEmail.js file:

// src/VerifyEmail.js import {useState} from 'react' import {auth} from './firebase' import {sendEmailVerification} from 'firebase/auth'

Now, let’s add a state that will be responsible for disabling and enabling the Resend Email button based on whether or not the verification email has been sent. This code goes after currentUser in the VerifyEmail component:

// src/VerifyEmail.js const [buttonDisabled, setButtonDisabled] = useState(false)

For the function that handles resending the verification email and disabling/enabling the button, we need this after the buttonDisabled state:

// src/VerifyEmail.js // ...  const resendEmailVerification = () => {   setButtonDisabled(true)   sendEmailVerification(auth.currentUser)   .then(() => {     setButtonDisabled(false)   }).catch((err) => {     alert(err.message)     setButtonDisabled(false)   }) }  // ...

Next, in the return statement, modify the Resend Email button like this:

// ... <button    onClick={resendEmailVerification}   disabled={buttonDisabled}   >Resend Email</button> // ...

Now, if we go over to the verify-email page and click the button, another email will be sent to us. But there is a problem with how we created this functionality because if we try to click the button again in less than a minute, we get an error from Firebase saying we sent too many requests. This is because Firebase has a one minute interval before being able to send another email to the same address. That’s the net thing we need to address.

What we need to do is make the button stay disabled for 60 seconds (or more) after a verification email is sent. We can enhance the user experience a bit by displaying a countdown timer in Resend Email button to let the user know the button is only temporarily disabled.

In the VerifyEmail.js file, add a useEffect import:

import {useState, useEffect} from 'react'

Next, add the following after the buttonDisabled state:

// src/VerifyEmail.js const [time, setTime] = useState(60) const [timeActive, setTimeActive] = useState(false)

In the above code, we have created a time state which will be used for the 60-second countdown and also a timeActive state which will be used to control when the count down will start.

Add the following lines of code after the states we just created:

// src/VerifyEmail.js // ...  useEffect(() => {   let interval = null   if(timeActive && time !== 0 ){     interval = setInterval(() => {       setTime((time) => time - 1)     }, 1000)   }else if(time === 0){     setTimeActive(false)     setTime(60)     clearInterval(interval)   }   return () => clearInterval(interval); }, [timeActive, time])  // ...

In the above code, we created a useEffect hook that only runs when the timeActive or time state changes. In this hook, we are decreasing the previous value of the time state by one every second using the setInterval method, then we are stopping the decrementing of the time state when its value equals zero.

Since the useEffect hook is dependent on the timeActive and time state, one of these states has to change before the time count down can start. Changing the time state is not an option because the countdown has to start only when a verification email has been sent. So, instead, we need to change the timeActive state.

In the resendEmailVerification function, modify the .then method of sendEmailVerification to look like this:

// src/VerifyEmail.js // ... .then(() => {   setButtonDisabled(false)   setTimeActive(true) }) // ...

Now, when an email is sent, the timeActive state will change to true and the count down will start. In the code above we need to change how we are disabling the button because, when the count down is active, we want the disabled button.

We will do that shortly, but right now, let’s make the countdown timer visible to the user. Modify the Resend Email button to look like this:

// src/VerifyEmail.js <button    onClick={resendEmailVerification}   disabled={buttonDisabled} >Resend Email {timeActive && time}</button>

To keep the button in a disabled state while the countdown is active, let’s modify the disabled attribute of the button to look like this:

disabled={timeActive}

With this, the button will be disabled for a minute when a verification email is sent. Now we can go ahead and remove the buttonDisabled state from our code.

Although this functionality works, there is still one problem with how we implemented it: when a user registers and is taken to the verify-email page when they have not received an email yet, they may try to click the Resend Email button, and if they do that in less than a minute, Firebase will error out again because we’ve made too many requests.

To fix this, we need to make the Resend Email button disabled for 60 seconds after an email is sent to the newly registered user. This means we need a way to change the timeActive state within the Register component. We can also use the Context API for this. It will allow us to globally manipulate and access the timeActive state.

Let’s make a few modifications to our code to make things work properly. In the VerifyEmail component, cut the timeActive state and paste it into the App component after the currentUser state.

// src/App.js function App() {   // ...   const [timeActive, setTimeActive] = useState(false)    // ...

Next, put timeActive and setTimeActive inside the object of AuthProvider value prop. It should look like this:

// src/App.js // ... <AuthProvider value={{currentUser, timeActive, setTimeActive}}> // ... 

Now we can access timeActive and setTimeActive within the children of AuthProvider. To fix the error in our code, go to the VerifyEmail.js file and de-structure both timeActive and setTimeActive from useAuthProvider:

// src/VerifyEmail.js const {timeActive, setTimeActive} = useAuthValue()

Now, to change the timeActive state after a verification email has been sent to the registered user, add the following import in the Register.js file:

// src/Register.js import {useAuthValue} from './AuthContext'

Next, de-structure setTimeActive from useAuthValue with this snippet among the other states in the Register component:

// src/Register.js const {setTimeActive} = useAuthValue()

Finally, in the register function, set the timeActive state with the .then the method of sendEmailVerification:

// src/Register.js // ... .then(() => {   setTimeActive(true)   history.push('/verify-email') }) // ...

With this, a user will be able to send a verification email without getting any errors from Firebase.

The last thing to fix concerning user verification is to take the user to their profile page after they have verified their email. To do this, we will use a reload function in the currentUser object. It allows us to reload the user object coming from Firebase, that way we will know when something has changed.

First, let’s make the needed imports. In the VerifyEmail.js file, let’s add this:

// src/VerifyEmail.js import {useHistory} from 'react-router-dom'

We are importing useHistory so that we can use to navigate the user to the profile page. Next, add the following line of code after the states:

// src/VerifyEmail.js const history = useHistory()

And, finally, add the following lines of code after the history variable:

// src/VerifyEmail.js // ...  useEffect(() => {   const interval = setInterval(() => {     currentUser?.reload()     .then(() => {       if(currentUser?.emailVerified){         clearInterval(interval)         history.push('/')       }     })     .catch((err) => {       alert(err.message)     })   }, 1000) }, [history, currentUser])  // ...

In the above code, we are running the reload function every one second until the user’s email has been verified, and, if it has, we are navigating the user to their profile page.

To test this, let’s verify our email by following the instructions in the email sent from Firebase. If all is good, we will be automatically taken to our profile page.

Right now the profile page is showing no user data and he Sign Out link does not work. That’s ur next task.

Working on the user profile page

Let’s start by displaying the Email and Email verified values. For this, we will make use of the currentUser state in AuthContext. What we need to do is import useAuthValue, de-structure currentUser from it, and then display the Email and Email verified value from the user object.

Here is what the Profile.js file should look like:

// src/Profile.js import './profile.css' import {useAuthValue} from './AuthContext'  function Profile() {   const {currentUser} = useAuthValue()    return (     <div className='center'>       <div className='profile'>         <h1>Profile</h1>         <p><strong>Email: </strong>{currentUser?.email}</p>         <p>           <strong>Email verified: </strong>           {`$ {currentUser?.emailVerified}`}         </p>         <span>Sign Out</span>       </div>     </div>   ) }  export default Profile

With this, the Email and Email verified value should now be displayed on our profile page.

To get the sign out functionality working, we will use the signOut function. It takes only one argument, which is the auth instance. So, in Profile.js. let’s add those imports.

// src/Profile.js import { signOut } from 'firebase/auth'  import { auth } from './firebase'

Now, in the return statement, modify the <span> that contains “Sign Out” so that is calls the signOut function when clicked:

// src/Profile.js // ... <span onClick={() => signOut(auth)}>Sign Out</span> // ...

Creating a Private Route for the Profile component

Right now, even with an unverified email address, a user can access the profile page. We don’t want that. Unverified users should be redirected to the login page when they try to access the profile. This is where private routes come in.

In the src directory, let’s create a new PrivateRoute.js file and add the following code to it:

// src/PrivateRoute.js import {Route, Redirect} from 'react-router-dom' import {useAuthValue} from './AuthContext'  export default function PrivateRoute({component:Component, ...rest}) {   const {currentUser} = useAuthValue()    return (     <Route       {...rest}       render={props => {         return currentUser?.emailVerified ? <Component {...props} /> : <Redirect to='/login' />     }}>     </Route>   ) }

This PrivateRoute is almost similar to using the Route. The difference is that we are using a render prop to redirect the user to the profile page if their email is unverified.

We want the profile page to be private, so well import PrivateRoute:

// src/App.js import PrivateRoute from './PrivateRoute'

Then we can replace Route with PrivateRoute in the Profile component. The Profile route should now look like this:

// src/App.js <PrivateRoute exact path="/" component={Profile} />

Nice! We have made the profile page accessible only to users with verified emails.

Creating login functionality

Since only users with verified emails can access their profile page when logged in with the signInWithEmailAndPassword function, we also need to check if their email has been verified and, if it is unverified, the user should be redirected to the verify-email page where the sixty-second countdown should also start.

These are the imports we need to add to the Login.js file:

import {signInWithEmailAndPassword, sendEmailVerification} from 'firebase/auth' import {auth} from './firebase' import {useHistory} from 'react-router-dom' import {useAuthValue} from './AuthContext'

Next, add the following line of code among the states in the Login component.

// src/Login.js const {setTimeActive} = useAuthValue() const history = useHistory()

Then add the following function after the history variable:

// src/Login.js // ...  const login = e => {   e.preventDefault()   signInWithEmailAndPassword(auth, email, password)   .then(() => {     if(!auth.currentUser.emailVerified) {       sendEmailVerification(auth.currentUser)       .then(() => {         setTimeActive(true)         history.push('/verify-email')       })     .catch(err => alert(err.message))   }else{     history.push('/')   }   })   .catch(err => setError(err.message)) }  // ...

This logs in a user and then check if whether they are verified or not. If they are verified, we navigate them to their profile page. But if they are unverified, we send a verification email, then redirect them to the verify-email page.

All we need to do to make this work is call the login function when the form is submitted. So, let’s modify the opening tag of the login_form to this:

// src/Login.js <form onSubmit={login} name='login_form'>

And, hey, we’re done!

Conclusion

In this tutorial, we have learned how to use version 9 of the Firebase Authentication to build a fully functioning user registration and authentication service in React. Is it super easy? No, there are a few thing we need to juggle. But is it a heck of a lot easier than building our own service from scratch? You bet it is! And that’s what I hope you got from reading this.

References


User Registration and Auth Using Firebase and React originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , , , ,
[Top]

Remember You Are Not the User

One thing people can do to make their websites better is to remember that you are not representative of all your users. Our life experiences and how we interact with the web are not indicative of how everyone interacts with the web.

We must care about accessibility.

Some users rely on assistive technology to navigate web pages. 

We must care about multi-language support.

Layouts that make sense to me, as a native English speaker (a left-to-right language) don’t necessarily make sense in Arabic (a right-to-left language) and can’t simply be swapped from a content perspective.

We must care about common/familiar UX paradigms.

What may be obvious to you may not be obvious to other users.

Take the time to research your key user markets and understand how these users expect your website or product to function. Don’t forget accessibility. Don’t forget internationalization. And don’t forget that you are not the representation of all users.

CSS-Tricks

,
[Top]

On User Tracking and Industry Standards on Privacy

Inspired by Eva PenzeyMoog’s new book, Jeremy highlights the widespread user tracking situation in this industry:

There was a line that really stood out to me:

The idea that it’s alright to do whatever unethical thing is currently the industry norm is widespread in tech, and dangerous.

It stood out to me because I had been thinking about certain practices that are widespread, accepted, and yet strike me as deeply problematic. These practices involve tracking users.

And ends with zero minced words:

We should stop stop tracking users because it’s wrong.

I take notice here, as I’m largely complicit when it comes to some degree of user tracking. For example, I have Google Analytics on this site. And pertinent to the topic: I have for well over a decade. I mention that not to prove that it’s OK, but almost to question it more, because it’s such a widespread long-term industry standard that is rarely questioned.

Because I have Google Analytics¹ on this site, I can take zoomed-out looks at the long-term traffic on this site. Here’s a 10-year period:

I realize that even this screenshot of a chart may be abhorrent to some, as it was collected from users who did not explicitly consent.

Or I can see how year-over-year mobile traffic on this site has gone down nearly 6%.

Weird.

I don’t send any personal information to Google Analytics. I don’t know who did what — I can only see anonymous aggregate data. Not only is it literally against Google policy to do so:

The Analytics terms of service, which all Analytics customers must adhere to, prohibits sending personally identifiable information (PII) to Analytics (such as names, social security numbers, email addresses, or any similar data), or data that permanently identifies a particular device.

… but I have a much clearer ethical line in my head there — that’s not something I’m comfortable with. Even when I’ve implemented user tracking that does tie a particular user to a particular action, it’s still anonymized such that it’s impossible for me to tell from using that tool who has done what.

But I understand that even this “anonymous” tracking is what is being questioned here. For example, just because what I send is anonymous, it doesn’t mean that attempts can’t be made to try to figure out exactly who is doing what by whoever has that data.

Switching the focus to email, I do use MailChimp to send the email newsletter on this site, and I haven’t done anything special with the settings to increase or decrease how much tracking happens when a newsletter is sent. As such, I can see data, like how many people I send to, how many open it, and how many clicks happened:

As I write this, I’m poking around in the reporting section to see what else I can see. Ughghk, guess what? I can literally see exactly who opened the email (by the person’s email address) and which links they clicked. I didn’t even realize that until now, but wow, that’s very super personally identifiable analytics information. I’m going to look into how I can turn that off because it does cross an ethical line for me.

There is also a brand new mini-war happening with email tracking (not the first, as I remember the uproar when Gmail started proxying images through their own servers, thus “breaking” the accuracy tracker pixel images). This time, it’s Apple doing more aggressive blocking, and companies like MailChimp having to tell customers it is going to mess with their analytics:

Apple Mail in macOS Monterey

Warning on the MailChimp reporting screen

I’m interested not just in the ethical concerns and my long-time complacency with industry norms, but also as someone who very literally sells advertising. I can tell you these things are true:

  • I have meetings about pricing where the decisions are based on the historical performance of what is being sold, meaning impressions and clicks.
  • The vast majority of first conversations between bag-of-money-holding advertisers and publishers like me, the very first questions I’m asked are about performance metrics.

That feels largely OK to me. When I go to the store to buy walnuts, I want to know how many walnuts I’m going to get for my dollar. I expect the store to price the walnuts based on normal economic factors, like how much they cost and the supply/demand for walnuts. The advertising buyers are the walnut buyers — they want to know what kind of performance an ad is likely to get for their dollar.

What if I said: I don’t know? I don’t know how many people see these ads. I don’t know how many people click these ads. I don’t know where they are from. I don’t know anything at all. And more, you aren’t allowed to know either. You can give me a URL to send them to, but it cannot have tracking params on it and we won’t be tracking the clicks on it.

Would I lose money? I gotta tell you readers: yes. In the short-term, anyway. It’s hard enough to land advertisers as it is. Coming off as standoffish and unwilling to tell them how many walnuts they are going to get for their dollar is going to make them roll their eyes and move on. Long-term, I bet it could be done. Tell advertisers (and the world) up front, very clearly, your stance on user tracking and how it means that you don’t have and won’t provide numbers via tracking. Lean on supply and demand entirely. Price spots at $ X to start. If other people have interest in the spot, raise the price until it stops selling, lower the price if it does. I bet it could be done.

To be honest, I’m not ready to tip my apple cart yet. I have a mortgage. I have employees to pay. I absolutely do not have a war chest to dip into to ride out a major income shortage. If I lost most of my advertising income I would just… fail. Close up shop. Be forced to make other dramatic life changes to deal with it. And I just don’t want to. It doesn’t feel like rolling the dice, because that implies I might win big. But if I were to take a hardline stance with advertisers, telling them that I provide zero data, “winning big” is merely getting back to the baseline for me.


I write all this just to help me think about it. I don’t want to sound like I’m being defensive. If I come across that way, I’d blame my own inertia for following what have felt like industry standards for so long, and being indoctrinated that those practices are just fine. I don’t feel like I’m crossing major ethical boundaries at the moment, but I’d rather be someone who questions myself and takes action when appropriate rather than tying a bandana over my eyes.

  1. I have tried other analytics services, like Plausible, that are more specifically privacy-focused.

The post On User Tracking and Industry Standards on Privacy appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Using CSS Shapes for Interesting User Controls and Navigation

Straight across or down, that’s the proverbial order for user controls on a screen. Like a list of menu items. But what if we change that to a more fluid layout with bends, curves, and nooks? We can pull it off with just a few lines of code. In the age of modern minimalistic designs, curved layouts for the user controls add just the right amount of pep to a web design.

And coding them couldn’t be more easier, thanks to CSS Shapes.

CSS Shapes (notably, the shape-outside property) is a standard that assigns geometric shapes to float elements. The content then wraps around the floated element along the boundaries of those shapes.

The use cases for this standard are usually showcased as designs for textual, editorial content — where plain text flow along the shapes floating at their sides. However, in this post, in place of just plain text, we use user controls to see how these shapes can breathe some fluid silhouettes into their layouts.

For the first demo, here’s a design that can be used in product pages, where any product-related action controls can be aligned along the shape of the product itself.

<img src="bottle.png"> <div>   <input type="radio" name=blue checked> <br>   <input type="radio" name=blue> <br>   <input type="radio" name=blue> <br>   <input type="radio" name=blue> </div>
img {   height: 600px;   float: left;   shape-outside: url("bottle.png");   filter: brightness(1.5); } input {   -webkit-appearance: none;   appearance: none;   width: 25px;   height: 25px;   margin-left: 20px;   box-sizing: content-box;   border: 10px solid #231714;   border-radius: 50%;   background: linear-gradient(45deg, pink, beige);   cursor: pointer; }

The image of the bottle is floated left and given a shape boundary using the shape-outside property. The image itself is referenced for the shape.

Note: Only images with transparent backgrounds can produce shapes according to the silhouettes of the images.

The default style of the radio buttons is replaced with a custom style. Once the browser applies the shape to the floated image, the radio buttons automatically align themselves along the shape of the bottle.

Like this, we don’t have to bother with individually assigning positions for each radio button to create such a design. Any buttons later added will automatically be aligned with the buttons before them according to the shape of the bottle.

Here’s another example, inspired by the Wikipedia homepage. This is a perfect example of the sort of unconventional main menu layouts we’re looking at.

Screenshot of the Wikipedia home page, displaying the site logo above a world globe made out of puzzle pieces. Links to various languages float around the globe's edge, like English, Spanish, German, in blue. Each link has a light grey count of how many articles are available in each language.

It’s not too crazy to make with shape-outside:

<div>   <img src="earth.png">   <div class=l>     <a href="#">Formation</a><br>     <a href="#">Atmosphere</a><br>     <a href="#">Heat</a><br>     <a href="#">Gravitation</a>   </div> </div> <div>   <img src="earth.png">   <div class=r>     <a href="#">Moon</a><br>     <a href="#">Climate</a><br>     <a href="#">Rotation</a><br>     <a href="#">Orbit</a>   </div> </div>
img {   height: 250px;   float: left;   shape-outside: circle(40%); }  /* stack both sets of menus on the same grid cell */ main > div { grid-area: 1/1; }   /* one set of menus is flipped and moved sideways over the other */ .r { transform: rotatey(180deg) translatex(250px); }  /* links inside the flipped set of menus are rotated back */ .r > a {    display: inline-block;    transform: rotateY(180deg) translateX(-40px);  }  /* hide one of the images */ main > div:nth-of-type(2) img { visibility: hidden; }

An element only ever floats left or right. There’s no center floating element where content wraps around both the sides. In order to achieve the design where links wrap on both the sides of the image, I made two sets of links and flipped one of the sets horizontally. I used the same image with a circle() CSS shape value in both the sets so the shapes match even after the rotation. The text of the links of the flipped set will appear upside down sideways, so it’s rotated back.

Although both the images can sit on top of each other with no visible overflow, it’s best to hide one of them with either opacity or visibility property.

The third example is a bit lively thanks to the use of one of the dynamic HTML elements, <details>. This demo is a good example for designs to show extra information on products and such which by default are hidden to the users.

<img src="diamond.png"> <details>   <summary>Click to know more!</summary>   <ul>     <li>The diamond is now known as the Sancy     <li>It comprises two back-to-back crowns     <li>It's likely of Indian origin   </ul> </details>
img {   height: 200px;   float: left;   shape-outside: url("diamond.png");   shape-margin: 20px; } summary {   background: red;   color: white;   cursor: pointer;   font-weight: bold;   width: 80%;    height: 30px;   line-height: 30px; }

The image is floated left and is given a CSS shape that’s same as the image. The shape-margin property adds margin space around the shape assigned to the floated element. When the <summary> element is clicked, the parent <details> element reveals its content that automatically wraps along the shape of the floated diamond image.

The content of the <details> element doesn’t necessarily have to be a list, like in the demo. Any inline content would wrap along the floated image’s shape.

The final example works with a polygon shape instead of images or simple shapes like circle and ellipse. Polygons give us more angular geometric shapes that can easily be bent by adding just another coordinate in the shape.

<img src="nasa.png"> <div><!-- triangle --></div> <ul>   <li><a href="#">Home</a>   <li><a href="#">Projects</a>   <li><a href="#">Shop</a>   <li><a href="#">Contact</a>   <li><a href="#">Media</a> </ul>
div {   width: 0;   height: 0;   --d:  200px;   /* red triangle */   border-right: var(--d) solid transparent;   border-bottom: var(--d) solid transparent;   border-left: var(--d) solid red;   float: left;   /* triangle CSS shape */   shape-outside: polygon(0 0, var(--d) 0, 0 var(--d)); } ul {   list-style: none;   padding-top: 25px; }

A left-floated red triangle is created using border properties. To create a triangular CSS shape that matches the red triangle, we’re using the polygon function as a value for the shape-outside property. The value for the polygon() function is the three coordinates of the triangle, separated by commas. The links automatically align around the floated triangle, forming a slanted menu layout down the triangle’s hypotenuse.

As you can see, even for a simple diagonal layout of user controls, CSS Shapes do a nice job adding a little pizzazz to a design. Using CSS Shapes is a much better option than rotating a line of user controls — the alignment of the individual controls and text also rotate, creating layout weirdness. By contrast, a CSS Shape simply lays out the individual controls along the provided shape’s boundary.


The post Using CSS Shapes for Interesting User Controls and Navigation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

Handling User Permissions in JavaScript

So, you have been working on this new and fancy web application. Be it a recipe app, a document manager, or even your private cloud, you‘ve now reached the point of working with users and permissions. Take the document manager as an example: you don’t just want admins; maybe you want to invite guests with read-only access or people who can edit but not delete your files. How do you handle that logic in the front end without cluttering your code with too many complicated conditions and checks?

In this article, we will go over an example implementation on how you could handle these kinds of situation in an elegant and clean manner. Take it with a grain of salt – your needs might differ, but I hope that you can gain some ideas from it.

Let‘s assume that you already built the back end, added a table for all users in your database, and maybe provided a dedicated column or property for roles. The implementation details are totally up to you (depending on your stack and preference). For the sake of this demo, let’s use the following roles:

  • Admin: can do anything, like creating, deleting, and editing own or foreign documents.
  • Editor: can create, view, and edit files but not delete them.
  • Guest: can view files, simple as that.

Like most modern web applications out there, your app might use a RESTful API to communicate with the back end, so let’s use this scenario for the demo. Even if you go with something different like GraphQL or server-side rendering, you can still apply the same pattern we are going to look at.

The key is to return the role (or permission, if you prefer that name) of the currently logged-in user when fetching some data.

{   id: 1,   title: "My First Document",   authorId: 742,   accessLevel: "ADMIN",   content: {...} }

Here, we fetch a document with some properties, including a property called accessLevel for the user’s role. That’s how we know what the logged-in user is allowed or not allowed to do. Our next job is to add some logic in the front end to ensure that guests don‘t see things they’re not supposed to, and vice-versa.

Ideally, you don’t only rely on the frontend to check permissions. Someone experienced with web technologies could still send a request without UI to the server with the intent to manipulate data, hence your backend should be checking things as well.

By the way, this pattern is framework agnostic; it doesn’t matter if you work with React, Vue, or even some wild Vanilla JavaScript.

Defining constants

The very first (optional, but highly recommended) step is to create some constants. These will be simple objects that contain all actions, roles, and other important parts that the app might consist of. I like to put them into a dedicated file, maybe call it constants.js:

const actions = {   MODIFY_FILE: "MODIFY_FILE",   VIEW_FILE: "VIEW_FILE",   DELETE_FILE: "DELETE_FILE",   CREATE_FILE: "CREATE_FILE" };  const roles = {   ADMIN: "ADMIN",   EDITOR: "EDITOR",   GUEST: "GUEST" };  export { actions, roles };

If you have the advantage of using TypeScript, you can use enums to get a slightly cleaner syntax.

Creating a collection of constants for your actions and roles has some advantages:

  • One single source of truth. Instead of looking through your entire codebase, you simply open constants.js to see what’s possible inside your app. This approach is also very extensible, say when you add or remove actions.
  • No typing errors. Instead of hand-typing a role or action each time, making it prone to typos and unpleasant debugging sessions, you import the object and, thanks to your favorite editor’s magic, get suggestions and auto-completion for free. If you still mistype a name, ESLint or some other tool will most likely yell at you until you fix it.
  • Documentation. Are you working in a team? New team members will appreciate the simplicity of not needing to go through tons of files to understand what permissions or actions exist. It can also be easily documented with JSDoc.

Using these constants is pretty straight-forward; import and use them like so:

import { actions } from "./constants.js";  console.log(actions.CREATE_FILE);

Defining permissions

Off to the exciting part: modeling a data structure to map our actions to roles. There are many ways to solve this problem, but I like the following one the most. Let’s create a new file, call it permissions.js, and put some code inside:

import { actions, roles } from "./constants.js";  const mappings = new Map();  mappings.set(actions.MODIFY_FILE, [roles.ADMIN, roles.EDITOR]); mappings.set(actions.VIEW_FILE, [roles.ADMIN, roles.EDITOR, roles.GUEST]); mappings.set(actions.DELETE_FILE, [roles.ADMIN]); mappings.set(actions.CREATE_FILE, [roles.ADMIN, roles.EDITOR]);

Let’s go through this, step-by-step:

  • First, we need to import our constants.
  • We then create a new JavaScript Map, called mappings. We could’ve gone with any other data structure, like objects, arrays, you name it. I like to use Maps, since they offer some handy methods, like .has(), .get(), etc.
  • Next, we add (or rather set) a new entry for each action our app has. The action serves as the key, by which we then get the roles required to execute said action. As for the value, we define an array of necessary roles.

This approach might seem strange at first (it did to me), but I learned to appreciate it over time. The benefits are evident, especially in larger applications with tons of actions and roles:

  • Again, only one source of truth. Do you need to know what roles are required to edit a file? No problem, head over to permissions.js and look for the entry.
  • Modifying business logic is surprisingly simple. Say your product manager decides that, from tomorrow on, editors are allowed to delete files; simply add their role to the DELETE_FILE entry and call it a day. The same goes for adding new roles: add more entries to the mappings variable, and you’re good to go.
  • Testable. You can use snapshot tests to make sure that nothing changes unexpectedly inside these mappings. It’s also clearer during code reviews.

The above example is rather simple and could be extended to cover more complicated cases. If you have different file types with different role access, for example. More on that at the end of this article.

Checking permissions in the UI

We defined all of our actions and roles and we created a map that explains who is allowed to do what. It’s time to implement a function for us to use in our UI to check for those roles.

When creating such new behavior, I always like to start with how the API should look. Afterwards, I implement the actual logic behind that API.

Say we have a React Component that renders a dropdown menu:

 function Dropdown() {   return (     <ul>       <li><button type="button">Refresh</button><li>       <li><button type="button">Rename</button><li>       <li><button type="button">Duplicate</button><li>       <li><button type="button">Delete</button><li>     </ul>   ); }

Obviously, we don’t want guests to see nor click the option “Delete” or “Rename,” but we want them to see “Refresh.” On the other hand, editors should see all but “Delete.” I imagine some API like this:

hasPermission(file, actions.DELETE_FILE);

The first argument is the file itself, as fetched by our REST API. It should contain the accessLevel property from earlier, which can either be ADMIN, EDITOR, or GUEST. Since the same user might have different permissions in different files, we always need to provide that argument.

As for the second argument, we pass an action, like deleting the file. The function should then return a boolean true if the currently logged-in user has permissions for that action, or false if not.

import hasPermission from "./permissions.js"; import { actions } from "./constants.js";  function Dropdown() {   return (     <ul>       {hasPermission(file, actions.VIEW_FILE) && (         <li><button type="button">Refresh</button></li>       )}       {hasPermission(file, actions.MODIFY_FILE) && (         <li><button type="button">Rename</button></li>       )}       {hasPermission(file, actions.CREATE_FILE) && (         <li><button type="button">Duplicate</button></li>       )}       {hasPermission(file, actions.DELETE_FILE) && (         <li><button type="button">Delete</button></li>       )}     </ul>   ); }

You might want to find a less verbose function name or maybe even a different way to implement the entire logic (currying comes to mind), but for me, this has done a pretty good job, even in applications with super complex permissions. Sure, the JSX looks more cluttered, but that’s a small price to pay. Having this pattern consistently used across the entire app makes permissions a lot cleaner and more intuitive to understand.

In case you are still not convinced, let’s see how it would look without the hasPermission helper:

return (   <ul>     {['ADMIN', 'EDITOR', 'GUEST'].includes(file.accessLevel) && (       <li><button type="button">Refresh</button></li>     )}     {['ADMIN', 'EDITOR'].includes(file.accessLevel) && (       <li><button type="button">Rename</button></li>     )}     {['ADMIN', 'EDITOR'].includes(file.accessLevel) && (       <li><button type="button">Duplicate</button></li>     )}     {file.accessLevel == "ADMIN" && (       <li><button type="button">Delete</button></li>     )}   </ul> );

You might say that this doesn’t look too bad, but think about what happens if more logic is added, like license checks or more granular permissions. Things tend to get out of hand quickly in our profession.

Are you wondering why we need the first permission check when everybody may see the “Refresh” button anyways? I like to have it there because you never know what might change in the future. A new role might get introduced that may not even see the button. In that case, you only have to update your permissions.js and get to leave the component alone, resulting in a cleaner Git commit and fewer chances to mess up.

Implementing the permission checker

Finally, it’s time to implement the function that glues it all together: actions, roles, and the UI. The implementation is pretty straightforward:

import mappings from "./permissions.js";  function hasPermission(file, action) {   if (!file?.accessLevel) {     return false;   }    if (mappings.has(action)) {     return mappings.get(action).includes(file.accessLevel);   }    return false; }  export default hasPermission; export { actions, roles };

You can put the above code into a separate file or even within permissions.js. I personally keep them together in one file but, hey, I am not telling you how to live your life. 🙂

Let’s digest what’s happening here:

  1. We define a new function, hasPermission, using the same API signature that we decided on earlier. It takes the file (which comes from the back end) and the action we want to perform.
  2. As a fail-safe, if, for some reason, the file is null or doesn’t contain an accessLevel property, we return false. Better be extra careful not to expose “secret” information to the user caused by a glitch or some error in the code.
  3. Coming to the core, we check if mappings contains the action that we are looking for. If so, we can safely get its value (remember, it’s an array of roles) and check if our currently logged-in user has the role required for that action. This either returns true or false.
  4. Finally, if mappings didn’t contain the action we are looking for (could be a mistake in the code or a glitch again), we return false to be extra safe.
  5. On the last two lines, we don’t only export the hasPermission function but also re-export our constants for developer convenience. That way, we can import all utilities in one line.
import hasPermission, { actions } from "./permissions.js";

More use cases

The shown code is quite simple for demonstration purposes. Still, you can take it as a base for your app and shape it accordingly. I think it’s a good starting point for any JavaScript-driven application to implement user roles and permissions.

With a bit of refactoring, you can even reuse this pattern to check for something different, like licenses:

import { actions, licenses } from "./constants.js";  const mappings = new Map();  mappings.set(actions.MODIFY_FILE, [licenses.PAID]); mappings.set(actions.VIEW_FILE, [licenses.FREE, licenses.PAID]); mappings.set(actions.DELETE_FILE, [licenses.FREE, licenses.PAID]); mappings.set(actions.CREATE_FILE, [licenses.PAID]);  function hasLicense(user, action) {   if (mappings.has(action)) {     return mappings.get(action).includes(user.license);   }    return false; }

Instead of a user’s role, we assert their license property: same input, same output, completely different context.

In my team, we needed to check for both user roles and licenses, either together or separately. When we chose this pattern, we created different functions for different checks and combined them in a wrapper. What we ended up using was a hasAccess util:

function hasAccess(file, user, action) {   return hasPermission(file, action) && hasLicense(user, action); }

It’s not ideal to pass three arguments each time you call hasAccess, and you might find a way around that in your app (like currying or global state). In our app, we use global stores that contain the user’s information, so we can simply remove the second argument and get that from a store instead.

You can also go deeper in terms of permission structure. Do you have different types of files (or entities, to be more general)? Do you want to enable certain file types based on the user‘s license? Let’s take the above example and make it slightly more powerful:

const mappings = new Map();  mappings.set(   actions.EXPORT_FILE,   new Map([     [types.PDF, [licenses.FREE, licenses.PAID]],     [types.DOCX, [licenses.PAID]],     [types.XLSX, [licenses.PAID]],     [types.PPTX, [licenses.PAID]]   ]) );

This adds a whole new level to our permission checker. Now, we can have different types of entities for one single action. Let‘s assume that you want to provide an exporter for your files, but you want your users to pay for that super-fancy Microsoft Office converter that you’ve built (and who could blame you?). Instead of directly providing an array, we nest a second Map inside the action and pass along all file types that we want to cover. Why using a Map, you ask? For the same reason I mentioned earlier: it provides some friendly methods like .has(). Feel free to use something different, though.

With the recent change, our hasLicense function doesn’t cut it any longer, so it’s time to update it slightly:

function hasLicense(user, file, action) {   if (!user || !file) {     return false;   }    if (mappings.has(action)) {     const mapping = mappings.get(action);      if (mapping.has(file.type)) {       return mapping.get(file.type).includes(user.license);     }   }    return false; }

I don’t know if it’s just me, but doesn’t that still look super readable, even though the complexity has increased?

Testing

If you want to ensure that your app works as expected, even after code refactorings or the introduction of new features, you better have some test coverage ready. In regards to testing user permissions, you can use different approaches:

  • Create snapshot tests for mappings, actions, types, etc. This can be achieved easily in Jest or other test runners and ensures that nothing slips unexpectedly through the code review. It might get tedious to update these snapshots if permissions change all the time, though.
  • Add unit tests for hasLicense or hasPermission and assert that the function is working as expected by hard-coding some real-world test cases. Unit-testing functions is mostly, if not always, a good idea as you want to ensure that the correct value is returned.
  • Besides ensuring that the internal logic works, you can use additional snapshot tests in combination with your constants to cover every single scenario. My team uses something similar to this:
Object.values(actions).forEach((action) => {   describe(action.toLowerCase(), function() {     Object.values(licenses).forEach((license) => {       it(license.toLowerCase(), function() {         expect(hasLicense({ type: 'PDF' }, { license }, action)).toMatchSnapshot();         expect(hasLicense({ type: 'DOCX' }, { license }, action)).toMatchSnapshot();         expect(hasLicense({ type: 'XLSX' }, { license }, action)).toMatchSnapshot();         expect(hasLicense({ type: 'PPTX' }, { license }, action)).toMatchSnapshot();       });     });   }); });

But again, there’re many different personal preferences and ways to test it.

Conclusion

And that’s it! I hope you were able to gain some ideas or inspiration for your next project and that this pattern might be something you want to reach for. To recap some of its advantages:

  • No more need for complicated conditions or logic in your UI (components). You can rely on the hasPermission function’s return value and comfortably show and hide elements based on that. Being able to separate business logic from your UI helps with a cleaner and more maintainable codebase.
  • One single source of truth for your permissions. Instead of going through many files to figure out what a user can or cannot see, head into the permissions mappings and look there. This makes extending and changing user permissions a breeze since you might not even need to touch any markup.
  • Very testable. Whether you decide on snapshot tests, integration tests with other components, or something else, the centralized permissions are painless to write tests for.
  • Documentation. You don’t need to write your app in TypeScript to benefit from auto-completion or code validation; using predefined constants for actions, roles, licenses, and such can simplify your life and reduce annoying typos. Also, other team members can easily spot what actions, roles, or whatever are available and where they are being used.

Suppose you want to see a complete demonstration of this pattern, head over to this CodeSandbox that plays around with the idea using React. It includes different permission checks and even some test coverage.

What do you think? Do you have a similar approach to such things and do you think it’s worth the effort? I am always interested in what other people came up with, feel free to post any feedback in the comment section. Take care!


The post Handling User Permissions in JavaScript appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]

Recipe websites, data modeling, and user experience

Simeon Griggs with some nice UX ideas for a recipe website:

  • No math. Swap between units and adjust servings on-the-fly.
  • Offer alternative ingredients.
  • Re-list the ingredient amounts when they’re referenced in the instructions.

I totally agree, especially on that last one:

Of all our improvements I think this is my favourite.

A typical recipe layout contains ingredients with amounts at the start. Then, a bullet point list of instructions to perform the method.

The method though typically does not reference those amounts again, so if you don’t prepare all your amounts ahead of time (which is what you’re probably supposed to do but come on who does that) you’ll have to keep scanning back and forward.

Part of what makes this stuff possible is how you set up the data model. For example, an ingredient might be an Array instead of a String so that you’re set up for offering alternatives right out of the gate.

Direct Link to ArticlePermalink


The post Recipe websites, data modeling, and user experience appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]

How Film School Helped Me Make Better User Experiences

Recently, I finished a sixty-day sprint where I posted hand-coded zombie themed CSS animation every day. I learned a lot, but it also took me back to film school and reminded me of so many things I learned about storytelling, cinematography, and art.

Turns out that much of what I learned back then is relevant to websites, particularly web animations. Sarah Drasner made the connection between theater and development and I thought I’d extend some of those ideas as they relate to film.

A story makes everything more engaging

Humans love stories. I don’t need to quote you statistics on the billions of dollars spent on shows and books and games each year. If you can inject story into a website — especially when it comes to animation — it’ll be that much more interesting and appealing to your audience.

There are many ways to define what a “story” is, but as far as things go for the web where animations can be quick or subtle, I think a story only requires two things: a character and an inciting incident (which is simply a plot point that brings the protagonist — or main character — into the story).

Take the “Magical Oops” demo I made over at CodePen:

There’s not much going on, but there is a story. We have a character, the scientist, who invokes an inciting incident when he fires the shrink ray at the zombie. Instead of shrinking the zombie, the ray shrinks the zombie’s hat to reveal (and ultimately be worn by) a rabbit. Will you necessarily relate to those characters? Probably not, at least personally. But the fact that something happens to them is enough of an engaging hook to draw you in.

Sure, I lean toward funny and silly storylines, but a story’s tone can be serious or any other number of things.

I’m confident you can find a story that fits your site.

A story makes everything more personable

Humans anthropomorphize anything and everything. You know exactly what that feels like if you’ve ever identified with characters in a Pixar movie, like “Toy Story” or “Inside Out.” The character you add doesn’t have to be a literal living thing or representative of a living thing. Heck, my stories are about the undead.

How does that relate to the web? Let’s say your app congratulates users when completing a task, like Slack does when all unread threads have been cleared out.

The point is to add some personality and intentionality to whatever movement you’re creating. It’s also about bringing the story — which is the user task of reviewing unread messages — to a natural (and, in this case, a happy) conclusion. That sort of feedback is not only informative, but something that makes the user part of the story in a personable way.

If a viewer can understand the subject of the story, they’ll get why something moves or changes. They’ll see it as a character — even if the subject is the user. That’s what makes something personable. (You got it! Here’s a pony. 🐴)

Watch for the human’s smirk in my “Undead Seat Driver” pen:

The smirk introduces an emotional element that further adds to the story by making the main character more relatable.

Direct attention with visual depth

One of the greatest zombie movies of all time, Citizen Kane, reached popularity for a variety of reasons. It’s a wonderful story with great acting, for one, but there’s something else you might not catch when viewing the movie today that was revolutionary at the time: deep focus photography. Deep focus allowed things in the foreground and the background and the middle ground to be in focus all at the same time. Before this, it was only possible to use one focal point at a time. Deep focus made the film almost feel like it was in 3D.

We’re not constrained by camera lenses on the web (well, aside from embedded media I suppose), but one thing that makes the deep focus photography of Citizen Kane work so well is that director Orson Welles was able to point a viewer’s attention at different planes at different times. He sometimes even had multiple things happening in multiple planes, but this was always a choice. 

Working with deep focus on the web has actually been happening for some time, even if it isn’t called that. Think of parallax scrolling and how it adds depth between backgrounds. There’s also the popular modal pattern where an element dominates the foreground while the background is either dimmed or blurred out.

That was the idea behind my “Hey, Hey, Hey!” pen that starts with a character in focus on a faraway plane who gives way to a zombie that appears in the foreground:

The opposite sort of thing occurs here in my “Nobody Here But Us Humans… 2” pen:

Try to think of a website as a 3D space and you’ll open up possibilities you may have never considered before. And while there are 3D transforms that work right now in your browser, that isn’t the only thing I’m talking about. There are tons of ways to “fake” a 3D effect using shading, shadows, relative size, blurs or other types of distortion.

For example, I used a stacking order to mimic a multi-dimensional space in my “Finally, alone with my sandwich…” pen. Notice how the human’s head rotation lends a little more credibility to the effect:

Take animation to the next level with scenes

Some of the work I’m proudest of are those where I went beyond silly characters doing silly things (although I am proud of that as well). There are two animations in particular that come to mind.

The first is what I call “Zombie Noon 2”:

The reason this one stands out to me is how the camera suddenly (and possibly as an unexpected plot twist) turns the viewer into a character in the story. Once the Zombie’s shots are fired, the camera rolls over, essentially revealing that it’s you who has been shot.

The second piece that comes to mind is called “Lunch (at) Noon” :

(I apparently got some middle school glee out of shooting hats off zombies’s heads. *shrugs* Being easily amused is cheap entertainment.)

Again, the camera puts things in a sort of first-person perspective where we’re facing a zombie chef who gets his hat shot off. The twist comes when a Ratatouille-like character is revealed under the hat, triggering a new scene by zooming in on him. Watch his eyes narrow when the focus turns to him.

Using the “camera” is an awesome way to bring an animation to the next level; it forces viewer participation. That doesn’t mean the camera should swoop and fly and zoom at every turn and with every animation, but switching from a 2D to a 3D perspective — when done well and done to deepen the experience — can enhance  a user’s experience with it.


So, as it turns out, my film school education really has paid off! There’s so much of it that directly applies to the web, and hopefully you see the same correlations that I’ve discovered.

I’d be remiss if I didn’t call out something important in this article. While I think borrowing concepts from stories and storytelling is really awesome and can be the difference between good and great experiences, they aren’t the right call in every situation. Like, what’s the point of putting a user through a story-like experience on a terms and conditions page? Legal content is typically already a somewhat tense read, so adding more tension may not be the best bet. But, hey, if you’re able to introduce a story that relieves the tension of that context, then by all means! and, let’s not forget about users who prefer reduced motion.

Bottom line: These ideas aren’t silver bullets for all cases. They’re tools to help you think about how you can take your site and your animations the extra mile and enhance a user’s experience in a pleasant way.


The post How Film School Helped Me Make Better User Experiences appeared first on CSS-Tricks.

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

CSS-Tricks

, , , , ,
[Top]

User agents

Jeremy beating the classic drum:

For web development, start with HTML, then CSS, then JavaScript (and don’t move on to JavaScript too quickly—really get to grips with HTML and CSS first).

And then…

That’s assuming you want to be a good well-rounded web developer. But it might be that you need to get a job as quickly as possible. In that case, my advice would be very different. I would advise you to learn React.

JEREMY! HAS YOUR WEBSITE BEEN STOLEN‽ BLINK TWICE IF YOU NEED HELP.

Just kidding, just kidding. I don’t disagree at all. I’m a fan of React, if you couldn’t tell. But I’ve also been around the block a few times and like to think I have a decent sense of what the right tools for the job are most of the time.

Make sure to read Jeremy’s post to see why it’s called “User agents.” He means it quite literally.

Direct Link to ArticlePermalink

The post User agents appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Better Form Inputs for Better Mobile User Experiences

Here’s one simple, practical way to make apps perform better on mobile devices: always configure HTML input fields with the correct type, inputmode, and autocomplete attributes. While these three attributes are often discussed in isolation, they make the most sense in the context of mobile user experience when you think of them as a team. 

There’s no question that forms on mobile devices can be time-consuming and tedious to fill in, but by properly configuring inputs, we can ensure that the data entry process is as seamless as possible for our users. Let’s take a look at some examples and best practices we can use to create better user experiences on mobile devices.

Use this demo to experiment on your own, if you’d like.

Using the correct input type

This is the easiest thing to get right. Input types, like email, tel, and url, are well-supported across browsers. While the benefit of using a type, like tel over the more generic text, might be hard to see on desktop browsers, it’s immediately apparent on mobile.

Choosing the appropriate type changes the keyboard that pops up on Android and iOS devices when a user focuses the field. For very little effort, just by using the right type, we will show custom keyboards for email, telephone numbers, URLs, and even search inputs

Text input type on iOS (left) and Android (right)
Email input type on iOS (left) and Android (right)
URL input type on iOS (left) and Android (right)
Search input type on iOS (left) and Android (right)

One thing to note is that both input type="email" and input type="url" come with validation functionality, and modern browsers will show an error tooltip if their values do not match the expected formats when the user submits the form. If you’d rather turn this functionality off, you can simply add the novalidate attribute to the containing form.

A quick detour into date types

HTML inputs comprise far more than specialized text inputs — you also have radio buttons, checkboxes, and so on. For the purposes of this discussion, though, I’m mostly talking about the more text-based inputs

There is a type of input that sits in the liminal space between the more free-form text inputs and input widgets like radio buttons: date. The date input type comes in a variety of flavors that are well-supported on mobile, including date, time, datetime-local, and month. These pop up custom widgets in iOS and Android when they are focused. Instead of triggering a specialized keyboard, they show a select-like interface in iOS, and various different types of widgets on Android (where the date and time selectors are particularly slick). 

I was excited to start using native defaults on mobile, until I looked around and realized that most major apps and mobile websites use custom date pickers rather than native date input types. There could be a couple reasons for this. First, I find the native iOS date selector to be less intuitive than a calendar-type widget. Second, even the beautifully-designed Android implementation is fairly limited compared to custom components — there’s no easy way to input a date range rather than a single date, for instance. 

Still, the date input types are worth checking out if the custom datepicker you’re using doesn’t perform well on mobile. If you’d like to try out the native input widgets on iOS and Android while making sure that desktop users see a custom widget instead of the default dropdown, this snippet of CSS will hide the calendar dropdown for desktop browsers that implement it:

::-webkit-calendar-picker-indicator {   display: none; }
Date input type on iOS (left) and Android (right)
Time input type on iOS (left) and Android (right)

One final thing to note is that date types cannot be overridden by the inputmode attribute, which we’ll discuss next.

Why should I care about inputmode?

The inputmode attribute allows you to override the mobile keyboard specified by the input’s type and directly declare the type of keyboard shown to the user. When I first learned about this attribute, I wasn’t impressed — why not just use the correct type in the first place? But while inputmode is often unnecessary, there are a few places where the attribute can be extremely helpful. The most notable use case that I’ve found for inputmode is building a better number input.

While some HTML5 input types, like url and email, are straightforward, input type="number" is a different matter. It has some accessibility concerns as well as a somewhat awkward UI. For example, desktop browsers, like Chrome, show tiny increment arrows that are easy to trigger accidentally by scrolling.

So here’s a pattern to memorize and use going forwards. For most numeric inputs, instead of using this: 

<input type="number" />

…you actually want to use this:

<input type="text" inputmode="decimal" />

Why not inputmode="numeric" instead of inputmode="decimal"

The numeric and decimal attribute values produce identical keyboards on Android. On iOS, however, numeric displays a keyboard that shows both numbers and punctuation, while decimal shows a focused grid of numbers that almost looks exactly like the tel input type, only without extraneous telephone-number focused options. That’s why it’s my preference for most types of number inputs.

iOS numeric input (left) and decimal input (right)
Android numeric input (left) and decimal input (right)

Christian Oliff has written an excellent article dedicated solely to the inputmode attribute.

Don’t forget autocomplete

Even more important than showing the correct mobile keyboard is showing helpful autocomplete suggestions. That can go a long way towards creating a faster and less frustrating user experience on mobile.

While browsers have heuristics for showing autocomplete fields, you cannot rely on them, and should still be sure to add the correct autocomplete attribute. For instance, in iOS Safari, I found that an input type="tel" would only show autocomplete options if I explicitly added a autocomplete="tel" attribute.

You may think that you are familiar with the basic autocomplete options, such as those that help the user fill in credit card numbers or address form fields, but I’d urge you to review them to make sure that you are aware of all of the options. The spec lists over 50 values! Did you know that autocomplete="one-time-code" can make a phone verification user flow super smooth?

Speaking of autocomplete…

I’d like to mention one final element that allows you to create your own custom autocomplete functionality: datalist. While it creates a serviceable — if somewhat basic — autocomplete experience on desktop Chrome and Safari, it shines on iOS by surfacing suggestions in a convenient row right above the keyboard, where the system autocomplete functionality usually lives. Further, it allows the user to toggle between text and select-style inputs.

On Android, on the other hand, datalist creates a more typical autocomplete dropdown, with the area above the keyboard reserved for the system’s own typeahead functionality. One possible advantage to this style is that the dropdown list is easily scrollable, creating immediate access to all possible options as soon as the field is focused. (In iOS, in order to view more than the top three matches, the user would have to trigger the select picker by pressing the down arrow icon.)

You can use this demo to play around with datalist:

And you can explore all the autocomplete options, as well as input type and inputmode values, using this tool I made to help you quickly preview various input configurations on mobile.

In summary

When I’m building a form, I’m often tempted to focus on perfecting the desktop experience while treating the mobile web as an afterthought. But while it does take a little extra work to ensure forms work well on mobile, it doesn’t have to be too difficult. Hopefully, this article has shown that with a few easy steps, you can make forms much more convenient for your users on mobile devices.

The post Better Form Inputs for Better Mobile User Experiences appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]