Tuesday, September 21, 2021

Cookieless Alternative To Embed HTML, CSS And JS Code Snippets

 Embedding code examples with third-party scripts often leads to tracking or cookies. We always wanted to have a simple website with a good UX, so setting cookies for no reason wasn’t an option for us. 

When we implement websites today, we are confronted by a lot of things we need to take care of. Ideally, we want to have a fast, secure, accessible, and fair website. At the same time, we want to have an interactive website with comments, polls, videos, code examples, and many more.

Together with a friend, I launched a tech blog last year and we ran exactly into that issue. We wanted to have a simple solution to embedding HTML, CSS, and JavaScript code examples but existing solutions often include tracking, cookies, a ton of features or bad performance. After some research, we realized that we had to build an alternative. Indiapen

Indiepen is a privacy-friendly, lightweight, and accessible solution to embed code examples. In fact, we don’t set any cookies or tracking!

Get Started

Indiepen can preview every website that follows a very simple convention. You need to provide a website with the following file structure:

.
├── index.html
├── main.js
└── styles.css

Deploy the code example with your favorite hosting provider (e.g. GitHub Pages, Netlify, or Vercel) and copy the URL. After that, go to our start page and use the code snippet generator.

The generated code looks like this:

<iframe class="indiepen"
  src="https://indiepen.tech/embed/?url=https%3A%2F%2Findiepen.tech%2Fexample%2F&tab=result"
  style="width: 100%; overflow: hidden; display: block; border: 0;"
  title="Indiepen Embed"
  loading="lazy"
  width="100%"
  height="450">
</iframe>

You can now use the code snippet and integrate it on your website. That’s it! You should now see your code example with an editor to discover the code.

Under The Hood

It sounds a bit strange nowadays but we haven’t used any JavaScript framework like React or Vue.js. It’s pure HTML, CSS and JavaScript with some help from Lea Verou’s Prism.js for syntax highlighting. Those libraries are very helpful to implement and maintain complex web applications but in our case, we just have three files we need to fetch and pass onto Prism.js.

Additionally, we have three buttons in the upper right corner to switch between the HTML, CSS, and JavaScript views. By introducing a UI framework, we couldn’t deliver a lightweight solution with less than 20 kb in size. For us, it was good learning, that UI libraries are important in our day-to-day business but we should carefully consider them and don’t forget the good old vanilla JavaScript.

 

Final Words 

Indiepen is open-source project and we are very excited to share our ideas with you. We would love to get feedback and have discussions about a fair web. Get in touch with me on Twitter or check out the project on GitHub.

Last but not least, I’d like to mention that Indiepen has a limited scope and we want to keep it simple by design. If you need to deal with more complex code examples, preprocessors for CSS or JavaScript, or you want to benefit from a platform to share your ideas, then please consider more sophisticated solutions like CodePen or JSFiddle.

Monday, September 20, 2021

Handling Mounting And Unmounting Of Navigation Routes In React Native

  Often you need two different sets of navigation stacks for pre and post user authentication. Usually, to see more content, you have to be authenticated in some way. Let’s look at how to mount and unmount navigation stack based on a met condition in React Native.

In this article, we are going to walk through mounting and unmounting of navigation routes in React Native. An expected behavior of your app is that once the authentication condition is met, a new set of navigation routes are available only to logged-in users, while the other screens which were displayed before authentication is removed and can’t be returned to unless the user signs out of the application.

For security in your app, protected routes provide you with a way to only display certain information/content on your app to specific users, while restricting access from unauthorized persons.

We will be working with Expo for this project because it’ll help us focus on the problem at hand instead of worrying about a lot of setups. The exact same steps in this article could be followed for a bare React Native application.

You need some familiarity with JavaScript and React Native to follow through with this tutorial. Here are a few important things you should already be familiar with:

  • Custom components in React Native (how to create components, receive, pass, and use props in a component). Read more.
  • React Navigation. Read more.
  • Stack Navigator in React Native. Read more.
  • Basic Knowledge of React Native Core components (<View/>, <Text/>, etc.). Read more.
  • React Native AsyncStorage. Read more.
  • Context API. Read more.

Project Setup And Base Authentication #

If you’re new to using expo and don’t know how to install expo, visit the official documentation. Once the installation is complete, go ahead to initialize a new React Native project with expo from our command prompt:

expo init navigation-project

You will be presented with some options to choose how you want the base setup to be.

In our case, let’s select the first option to set up our project as a blank document. Now, wait until the installation of the JavaScript dependencies is complete.

Once our app is set up, we can change our directory to our new project directory and open it in your favorite code editor. We need to install the library we will be using for AsyncStorage and our navigation libraries. Inside your folder directory in your terminal, paste the command above and choose a template (blank would work) to install our project dependencies.

Let’s look at what each of these dependencies is for:

  • @react-native-community/async-storage
    Like localStorage on the web, it is a React Native API for persisting data on a device in key-value pairs.
  • @react-native-community/masked-view, react-native-screens, react-native-gesture-handle
    These dependencies are core utilities that are used by most navigators to create the navigation structure in the app. (Read more in Getting started with React Native navigation.)
  • @react-navigation/native
    This is the dependency for React Native navigation.
  • @react-navigation/stack
    This is the dependency for stack navigation in React Native.
npm install @react-native-community/async-storage @react-native-community/masked-view @react-navigation/native @react-navigation/stack react-native-screens react-native-gesture-handle

To start the application use expo start from the app directory in your terminal. Once the app is started, you can use the expo app from your mobile phone to scan the bar code and view the application, or if you have an android emulator/IOS simulator, you can open the app through them from the expo developer tool that opens up in your browser when you start an expo application. For the images examples in this article, we will be using Genymotions to see our result.

Folder Structures #

Let us create our folder structure from the start so that it’s easier for us to work with it as we proceed:

We need two folders first:

  • context
    This folder will hold the context for our entire application as we will be working with Context API for global state management.
  • views
    This folder will hold both the navigation folder and the views for different screens.

Go ahead and create the two folders in your project directory.

Inside the context folder, create a folder called authContext and create two file inside of the authContext folder:

  • AuthContext.js,
  • AuthState.js.

We will need these files when we start working with Context API.

Now go to the views folder we created and create two more folders inside of it, namely:

  • navigation,
  • screens.

Now, we are not yet finished, inside the screens folder, create these two more folders:

  • postAuthScreens,
  • preAuthScreens.

Creating Our First Screen #

Now let’s create our first screen and call it the welcomeScreen.js inside the preAuthScreens folder.

preAuthScreens > welcomeScreen.js

Here’s the content of our welcomeScreen.js file:

import React from 'react';
import { View, Text, Button, StyleSheet, TextInput } from 'react-native';

const WelcomeScreen = () => {

  const onUserAuthentication = () => {
    console.log("User authentication button clicked")
  }

  return (
    <View style={styles.container}>
      <Text style={styles.header}>Welcome to our App!</Text>
      <View>
        <TextInput style={styles.inputs} placeholder="Enter your email here.." />
        <TextInput style={styles.inputs} secureTextEntry={true} placeholder="Enter your password here.." />
<Button  title="AUTHENTICATE" onPress={onUserAuthentication} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  header: {
    fontSize: 25,
    fontWeight: 'bold',
    marginBottom: 30
  },
  inputs: {
    width: 300,
    height: 40,
    marginBottom: 10,
    borderWidth: 1,
  }
})

export default WelcomeScreen

Here’s what we did in the code block above:

First, we imported the things we need from the React Native library, namely, View, Text, Button, TextInput. Next, we created our functional component WelcomeScreen.

You’ll notice that we imported the StyleSheet from React Native and used it to define styles for our header and also our <TextInput />.

Lastly, we export the WelcomeScreen component at the bottom of the code.

Now that we are done with this, let’s get this component to function as expected by using the useState hook to store the values of the inputs and update their states anytime a change happens in the input fields. We will also bring import the useCallback hook from React as we will be needing it later to hold a function.

First, while we are still in the WelcomeScreen component, we need to import the useState and useCallback from React.

import React, { useState, useCallback } from 'react';

Now inside the WelcomeScreen functional component, let’s create the two states for the email and password respectively:

...
const WelcomeScreen = () => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  return (
    ...
  )
}
...

Next, we need to modify our <TextInput /> fields so that the get their value from their respective states and update their state when the value of the input is updated:

import React, { useState, useCallback } from 'react';
import { View, Text, Button, StyleSheet, TextInput } from 'react-native';

const WelcomeScreen = () => {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  const onInputChange = (value, setState) => {
    setState(value);
  }
  return (
    <View>
      ...      
      <View>
        <TextInput
          style={styles.inputs}
          placeholder="Enter your email here.."
          value={email}
          onChangeText={(value) => onInputChange(value, setEmail)}
        />
        <TextInput
          style={styles.inputs}
          secureTextEntry={true}
          placeholder="Enter your password here.."
          value={password}
          onChangeText={(value) => onInputChange(value, setPassword)}
        />
        ...
      </View>
    </View>
  )
}
...

In the code above, here is what we did:

  • We made the value of each of the text inputs to point to their respective states.
  • We added the onChangeText handler to our text inputs. This fires up anytime a new value is entered or deleted from the input fields.
  • We called our onInputChange function which accepts two arguments:
    • The current value is supplied by the onChangeText handler.
    • The setter of the state that should be updated (for the first input field we pass setEmail and the second we pass setPassword.
    • Finally, we write our onInputChange function, and our function does only one thing: It updates the respective states with the new value.

The next thing we need to work on is the onUserAuthentication() function with is called whenever the button for the form submission is clicked.

Ideally, the user must have already created an account and login will involve some backend logic of some sort to check that the user exists and then assign a token to the user. In our case, since we are not using any backend, we will create an object holding the correct user login detail, and then only authenticate a user when the values they enter matches our fixed values from the login object of email and password that we will create.

Here’s the code we need to do this:

...

const correctAuthenticationDetails = {
  email: 'demouser@gmail.com',
  password: 'password'
}
const WelcomeScreen = () => {
  ...

  // This function gets called when the `AUTHENTICATE` button is clicked
  const onUserAuthentication = () => {
    if (
      email !== correctAuthenticationDetails.email ||
      password !== correctAuthenticationDetails.password
    ) {
      alert('The email or password is incorrect')
      return
    }
      // In here, we will handle what happens if the login details are       // correct
  }

  ...
  return (
    ...
  )
}
...

One of the first things you’ll notice in the code above is that we defined a correctAuthenticationDetails (which is an object that holds the correct login details we expect a user to supply) outside of the WelcomeScreen() functional component.

Next, we wrote the content of the onUserAuthentication() function and used a conditional statement to check if the email or password held in the respective states does not match the one we supplied in our object.

If you would like to see what we have done so far, import the WelcomeScreen component into your App.js like this:

Open the App.js file and put this replace the entire code with this:

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { View } from 'react-native';
import WelcomeScreen from './views/screens/preAuthScreens/welcomeScreen';
export default function App() {
  return (
    <View>
      <StatusBar style="auto" />
      <WelcomeScreen />
    </View>
  );
}

Looking closely at the code above, you’ll see that what we did was import the WelcomeScreen component and then used it in the App() function.

Now that we are done building the WelcomeScreen component, let’s move ahead and start working with Context API for managing our global state.

Why Context API? #

Using Context API, we do not need to install any additional library into ReactJS, it is less stressful to set up, and is one of the most popular ways of handling global state in ReactJS. For lightweight state management, it is a good choice.

Creating Our Context #

If you recall, we created a context folder earlier and created a subfolder inside of it called the authContext.

Now let’s navigate to the AuthContext.js file in the authContext folder and create our context:

context > authContext > AuthContext.js


import React, { createContext } from 'react';
const AuthContext = createContext();
export default AuthContext;

The AuthContext we just created holds the loading state value and the userToken state values. Currently, in the createContext we declared in the code-block above, we didn’t initialize any default values here so our context is currently undefined. An example value of the auth context could be {loading: false, userToken: 'abcd}

The AuthState.js file holds our Context API logic and their state values. Functions written here can be called from anywhere in our app and when they update values in state, it is updated globally also.

First, let’s bring in all the imports we will need in this file:

context > AuthContext > AuthState.js

import React, { useState } from 'react';
import AuthContext from './AuthContext';
import AsyncStorage from '@react-native-community/async-storage';

We imported the useState() hook from ReactJS to hold our states, we imported the AuthContext file we created above because this is where our empty context for authentication is initialized and we will need to use it as you’ll see later on while we progress, finally we import the AsyncStorage package (similar to localStorage for the web).

AsyncStorage is a React Native API that allows you to persist data offline over the device in a React Native application.

...

const AuthState = (props) => {
    const [userToken, setUserToken] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    const onAuthentication = async() => {
        const USER_TOKEN = "drix1123q2"
        await AsyncStorage.setItem('user-token', USER_TOKEN);
        setUserToken(USER_TOKEN);
        console.warn("user has been authenticated!")
    }

    return (
        <AuthContext.Provider
            value={{
                onAuthentication,
            }}
        >
            {props.children}
        </AuthContext.Provider>
    )
}
export default AuthState;

In the code block above here’s what we did:

  • We declared two states for the userToken and isLoading. The userToken state will be used to store the token saved to AsyncStorage, while the isLoading state will be used to track the loading status (initially it is set to true). We will find out more about the use of these two states as we proceed.

  • Next, we wrote our onAuthentication() function. This function is an async function that gets called when the login button is clicked from the welcomeScreen.jsx file. This function will only get called if the email and password the user has supplied matches the correct user detail object we provided. Usually what happens during authentication is that a token is generated for the user after the user is authenticated on the backend using a package like JWT, and this token is sent to the frontend. Since we are not going into all of that for this tutorial, we created a static token and kept it in a variable called USER_TOKEN.

  • Next, we use the await keyword to set our user token to AsyncStorage with the name user-token. The console.warn() statement is just used to check that everything went right, you can take it off whenever you like.

  • Finally, we pass our onAuthenticated function as a value inside our <AuthContext.Provider> so that we can access and call the function from anywhere in our app.

screens > preAuth > welcomeScreen.js

First, import useContext from ReactJS and import the AuthContext from the AuthContext.js file.

import React, { useState, useContext } from 'react';
import AuthContext from '../../../context/authContext/AuthContext'
...

Now, inside the welcomeScreen() functional component, let’s use the context which we have created:

...
const WelcomeScreen = () => {
  const { onAuthentication } = useContext(AuthContext)
  const onUserAuthentication = () => {
    if (
      email !== correctAuthenticationDetails.email ||
      password !== correctAuthenticationDetails.password
    ) {
      alert('The email or password is incorrect')
      return
    }
    onAuthentication()
  }
  return (
    ...
  )
}
...

In the above code block, we destructured the onAuthentication function from our AuthContext and then we called it inside our onUserAuthentication() function and removed the console.log() statement which was there before now.

Right now, this will throw an error because we don’t yet have access to the AuthContext. To use the AuthContext anywhere in your application, we need to wrap the top-level file in our app with the AuthState (in our case, it is the App.js file).

Go to the App.js file and replace the code there with this:

import React from 'react';
import WelcomeScreen from './views/screens/preAuthScreens/welcomeScreen';
import AuthState from './context/authContext/AuthState'

export default function App() {
  return (
    <AuthState>
      <WelcomeScreen />
    </AuthState>
  );
}

We’ve come so far and we’re done with this section. Before we move into the next section where we set up our routing, let’s create a new screen. The screen we are about to create will be the HomeScreen.js file which is supposed to show up only after successful authentication.

Go to: screens > postAuth.

Create a new file called HomeScreen.js. Here’s the code for the HomeScreen.js file:

screens > postAuth > HomeScreen.js

import React from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const HomeScreen = () => {

  const onLogout = () => {
    console.warn("Logout button cliked")
  }

  return (
    <View style={styles.container}>
      <Text>Now you're authenticated! Welcome!</Text>
      <Button title="LOG OUT" onPress={onLogout} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
})

export default HomeScreen

For now, the logout button has a dummy console.log() statement. Later on, we will create the logout functionality and pass it to the screen from our context.

Setting Up Our Routes #

We need to create three (3) files inside our navigation folder:

  • postAuthNavigator.js,
  • preAuthNavigator.js,
  • AppNavigator.js.

Once you’ve created these three files, navigate to the preAuthNaviagtor.js file you just created and write this:

navigation > preAuthNavigator.js

import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import WelcomeScreen from "../screens/preAuthScreens/welcomeScreen";

const PreAuthNavigator = () => {
    const { Navigator, Screen } = createStackNavigator();

    return (
        <Navigator initialRouteName="Welcome">
            <Screen
                name="Welcome"
                component={WelcomeScreen}
            />
        </Navigator>
    )
}
export default PreAuthNavigator;

In the file above, here’s what we did:

  • We imported the createStackNavigator from the @react-navigation/stack which we are using for our stack navigation. The createStackNavigatorProvides a way for your app to transition between screens where each new screen is placed on top of a stack. By default the stack navigator is configured to have the familiar iOS and Android look & feel: new screens slide in from the right on iOS, fade in from the bottom on Android. Click here if you want to learn more about the stack navigator in React Native.
  • We destructured Navigator and Screen from the createStackNavigator().
  • In our return statement, we created our navigation with the <Navigator/> and created our screen with the <Screen/>. this means that if we had multiple screens that can be accessed before authentication, we will have multiple <Screen/> tags here representing them.
  • Finally, we export our PreAuthNavigator component.

Let us do a similar thing for the postAuthNavigator.js file.

navigation > postAuthNavigator.js

import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import HomeScreen from "../screens/postAuthScreens/HomeScreen";
const PostAuthNavigator = () => {
  const { Navigator, Screen} = createStackNavigator();
  return (
    <Navigator initialRouteName="Home">
      <Screen
        name="Home"
        component={HomeScreen}
      />
    </Navigator> 
  )
}
export default PostAuthNavigator;

As we see in the code above, the only difference between the preAuthNavigator.js and the postAuthNavigator.js is the screen being rendered. While the first one takes the WelcomeScreen, the postAuthNavigator.js takes the HomeScreen.

To create our AppNavigator.js we need to create a few things.

Since the AppNavigator.js is where we will be switching and checking which route will be available for access by the user, we need several screens in place for this to work properly, let’s outline the things we need to create first:

  1. TransitionScreen.js
    While the app decides which navigation it is going to mount, we want a transition screen to show up. Typically, the transition screen will be a loading spinner or any other custom animation chosen for the app, but in our case, we will use a basic <Text/> tag to display loading….
  2. checkAuthenticationStatus()
    This function is what we will be calling to check the authentication status which will determine which navigation stack is going to be mounted. We will create this function in our context and use it in the Appnavigator.js.

Now, let’s go ahead and create our TransitionScreen.js file.

screens > TransitionScreen.js

import React from 'react';
import { Text, View } from 'react-native';

const TransitionScreen = () => {
  return (
    <View>
      <Text>Loading...</Text>
    </View>
  )
}

export default TransitionScreen

Our transition screen is just a simple screen that shows loading text. We will see where to use this as we proceed in this article.

Next, let us go to our AuthState.js and write our checkAuthenticationStatus():

context > authContext > AuthState.js

import React, { useState, useEffect } from 'react';
import AuthContext from './AuthContext';
import AsyncStorage from '@react-native-community/async-storage';

const AuthState = (props) => {
    const [userToken, setUserToken] = useState(null);
    const [isLoading, setIsLoading] = useState(true);

    ...
    useEffect(() => {
        checkAuthenticationStatus()
    }, [])
    
    const checkAuthenticationStatus = async () => {
        try {
            const returnedToken = await AsyncStorage.getItem('user-toke             n');
            setUserToken(returnedToken);
            console.warn('User token set to the state value)
        } catch(err){
            console.warn(`Here's the error that occured while retrievin             g token: ${err}`) 
        }
        setIsLoading(false)
    }


    const onAuthentication = async() => {
        ...
    }

    return (
        <AuthContext.Provider
            value={{
                onAuthentication,
                userToken,
                isLoading,
            }}
        >
            {props.children}
        </AuthContext.Provider>
    )
}
export default AuthState;

In the code block above, we wrote the function checkAuthenticationStatus(). In our function, here’s what we are doing:

  • We used the await keyword to get our token from AsyncStorage. With AsyncStorage, if there’s no token supplied, it returns null. Our initial userToken state is set to null also.
  • We use the setUserToken to set our returned value from AsyncStorage as our new userToken. If the returned value is null, it means our userToken remains null.
  • After the try{}…catch(){} block, we set isLoading to false because the function to check authentication status is complete. We’ll need the value of isLoading to know if we should still be displaying the TransitionScreen or not. It’s worth considering setting an error if there is an error retrieving the token so that we can show the user a “Retry” or “Try Again” button when the error is encountered.
  • Whenever AuthState mounts we want to check the authentication status, so we use the useEffect() ReactJS hook to do this. We call our checkAuthenticationStatus() function inside the useEffect() hook and set the value of isLoading to false when it is done.
  • Finally, we add our states to our <AuthContext.Provider/> values so that we can access them from anywhere in our app covered by the Context API.

Now that we have our function, it is time to go back to our AppNavigator.js and write the code for mounting a particular stack navigator based on the authentication status:

navigation > AppNavigator.js

First, we will import all we need for our AppNavigator.js.

import React, { useEffect, useContext } from "react";
import PreAuthNavigator from "./preAuthNavigator";
import PostAuthNavigator from "./postAuthNavigator";
import { NavigationContainer } from "@react-navigation/native"
import { createStackNavigator } from "@react-navigation/stack";
import AuthContext from "../../context/authContext/AuthContext";
import TransitionScreen from "../screens/TransitionScreen";

Now that we have all our imports, let’s create the AppNavigator() function.

...
const AppNavigator = () => {

}

export default AppNavigator

Next, we will now go ahead to write the content of our AppNavigator() function:

import React, { useState, useEffect, useContext } from "react";
import PreAuthNavigator from "./preAuthNavigator";
import PostAuthNavigator from "./postAuthNavigator";
import { NavigationContainer } from "@react-navigation/native"
import { createStackNavigator } from "@react-navigation/stack";
import AuthContext from "../../context/authContext/AuthContext";
import TransitionScreen from "../screens/transition";

const AppNavigator = () => {
    const { Navigator, Screen } = createStackNavigator();
    const authContext = useContext(AuthContext);
    const { userToken, isLoading } = authContext;
    if(isLoading) {
      return <TransitionScreen />
    }
    return (
    <NavigationContainer>
      <Navigator>
        { 
          userToken == null ? (
            <Screen
              name="PreAuth"
              component={PreAuthNavigator}
              options={{ header: () => null }}
            />
          ) : (
            <Screen 
              name="PostAuth"
              component={PostAuthNavigator}
              options={{ header: () => null }}
            />
          )
        }
      </Navigator>
    </NavigationContainer>
  )
}

export default AppNavigator

In the above block of code, here’s an outline of what we did:

  • We created a stack navigator and destructured the Navigator and Screen from it.
  • We imported the userToken and the isLoading from our AuthContext
  • When the AuthState mounts, the checkAuthenticationStatus() is called in the useEffecct hook there. We use the if statement to check if isLoading is true, if it is true the screen we return is our <TransitionScreen /> which we created earlier because the checkAuthenticationStatus() function is not yet complete.
  • Once our checkAuthenticationStatus() is complete, isLoading is set to false and we return our main Navigation components.
  • The NavigationContainer was imported from the @react-navigation/native. It is only used once in the main top-level navigator. Notice that we are not using this in the preAuthNavigator.js or the postAuthNavigator.js.
  • In our AppNavigator(), we still create a stack navigator. If the userToken gotten from our Context API is null, we mount the PreAuthNavigator, if its value is something else (meaning that the AsyncStorage.getItem() in the checkAuthenticationStatus() returned an actual value), then we mount the PostAuthNavigator. Our conditional rendering is done using the ternary operator.

Now we’ve set up our AppNavigator.js. Next, we need to pass our AppNavigator into our App.js file.

Let’s pass our AppNavigator into the App.js file:

App.js

 ...
import AppNavigator from './views/navigation/AppNavigator';

...
return (
    <AuthState>
      <AppNavigator />
    </AuthState>
  ); 

Adding The Logout Functionality #

At this point, our authentication and route selection process is complete. The only thing left for our app is to add the logout functionality.

The logout button is in the HomeScreen.js file. We passed an onLogout() function to the onPress attribute of the button. For now, we have a simple console.log() statement in our function, but in a little while that will change.

Now, let’s go to our AuthState.js and write the function for logout. This function simply clears the AsyncStorage where the user token is saved.

context > authContext > AuthState.js

...
const AuthState = (props) => {
    ...

    const userSignout = async() => {
        await AsyncStorage.removeItem('user-token');
        setUserToken(null);
    }


    return (
      ...
    )
}

export default AuthState;

The userSignout() is an asynchronous function that removes the user-token from our AsyncStorage.

Now we need to call the userSignout() function in our HomeScreen.js any time the logout button is clicked on.

Let’s go to our HomeScreen.js and use ther userSignout() from our AuthContext.

screens > postAuthScreens > HomeScreen.js

import React, { useContext } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import AuthContext from '../../../context/authContext/AuthContext'

const HomeScreen = () => {
  const { userSignout } = useContext(AuthContext)
  
  const onLogout = () => {
    userSignout()
  }
  return (
    <View style={styles.container}>
      <Text>Now you're authenticated! Welcome!</Text>
 <Button title="LOG OUT" onPress={onLogout} />
    </View>
  )
}
...

In the above code block we imported thee useContext hook from ReactJS, then we imported our AuthContext. Next, we destructured the userSignout function from our AuthContext and this userSignout() function is called in our onLogout() function.

Now whenever our logout button is clicked, the user token in our AsyncStorage is cleared.

Voila! our entire process is finished.

Here are some different behaviors we notice when using this pattern in our navigation stack switching:

  1. You’ll notice that there was nowhere we needed to make use of navigation.navigate() or navigation.push() to go to another route after login. Once our state is updated with the user token, the navigation stack rendered is automatically changed.
  2. Pressing the back button on your device after login is successful cannot take you back to the login page, instead, it closes the app entirely. This behavior is important because you don’t want the user to be able to return back to the login page except they log out of the app. The same thing applies to logging out — once the user logs out, they cannot use the back button to return to the HomeScreen screen, but instead, the app closes.

Conclusion #

In many Apps, authentication is one of the most important parts because it confirms that the person trying to gain access to protected content has the right to access the information. Learning how to do it right is an important step in building a great, intuitive, and easy to use/navigate the application.

Building on top of this code, here are a few things you might consider adding:

Here are also some important resources I found that will enlighten you more about authentication, security and how to do it right:

Resources #

 

 

 

 

 

 

Friday, September 17, 2021

ISR vs DPR: Big Words, Quick Explanation

 There are two strategies for incrementally building websites that are growing in popularity: Incremental Static Regeneration and Distributed Persistent Rendering. What’s the difference? Let’s figure it out.

If you’ve been dabbling in the Jamstack/page rendering/Next.js world, chances are you’ve heard of the terms “Incremental Static Regeneration” (ISR) and “distributed persistent rendering” (DPR) floating around. And if you haven’t, you might be like, “Wow, these are long words that I’ll never understand.” That’s where you’re wrong! You’re about to understand them now.

What Are These Things? #

These terms are strategies for incrementally building websites. Normally, when you deploy a website that isn’t server-side or client-side rendered, it has to be compiled and built for the browser to natively load it (so, for example, your JSX is transpiled to vanilla JavaScript, your SCSS compiled to vanilla CSS, your templates into HTML).

As your websites get large, you might start to run into having to have fairly long build times, because that’s a lot to compile. Generally, your websites might have pages in two categories:

  • Type A
    “Critical” pages (home page, about us, contact us)
  • Type B
    “Deferred” pages that might not be hit as often as the Type A pages (product catalog, certain documentation pages)

If you were to incrementally build this website, you could theoretically break up your build where your Type A pages are built upfront, and then Type B pages are built later!

Both ISR and DPR follow this approach but do it in slightly different ways.

What’s The Difference Between ISR And DPR? #

For both approaches, Type A pages are built upfront right when you deploy. With Type B pages, they vary a little more. The key thing that is different between these two approaches is immutability. When I say immutability, I mean that once a page is added to a build, it doesn’t change, and every user hitting a URL during that deployment will always see the exact same data.

With ISR, your Type B pages are built at runtime when a user goes to the page. Each of these pages has an “expiration time” (or “revalidation time”) where it will re-build based on new content that comes in at that time (fetched in the background). It is based on a caching strategy called “stale while revalidate”, meaning a page can be “stale” with old information until it’s re-generated and the cache is updated. This approach does not guarantee immutability across builds. When I say that, I mean that you can’t necessarily go back to a previous deployment with full confidence that all the content is going to be as it was at that time you originally ran that page.

There are pros and cons to this; the major pro meaning that you can have your data that populates the page regularly update without rebuilding the site, and the major con being that debugging will be very difficult without that immutable piece (because some users might see a stale page and others might see the correctly updated one).

With DPR, your Type B pages are also built at runtime when a user goes to the page. When the non-built pages are requested for the first time, they are built and cached at the edge so they don’t need to be built again. Once it’s a part of the build, it will not change until a redeploy. There are also pros and cons to this approach. The major pro is that this guarantees immutability. When you go to a URL, every user will always see the same exact information. The con is that if you want to have one of those pages updated, you’ll need to trigger a rebuild of the site.

How Can I Use These Approaches And Compare For Myself? #

In an ideal world, these approaches are built into the frameworks you use and you won’t have to do much to use them. Right now though, you can test it out with Next.js! ISR is built-in by default to Next.js when you deploy it to a Node.js-driven platform like Vercel, and DPR is built in when you deploy to Netlify.

You can use DPR in other frameworks as well (Zach Leatherman has some demos of it with Eleventy where he defers building hundreds of pages), and the Next.js team said that ISR will be coming to other frameworks in the future (like Nuxt and SvelteKit). You can also leave a comment on how DPR is implemented (for any Jamstack platform!) in this RFC.