Browsing Tag: React

    web design

    Building A Web App With React, Redux And Sanity.io — Smashing Magazine

    02/11/2021

    About The Author

    Ifeanyi Dike is a full-stack developer in Abuja, Nigeria. He’s the team lead at Sterling Digitals Limited but also open to more opportunities and …
    More about
    Ifeanyi

    Headless CMS is a powerful and easy way to manage content and access API. Built on React, Sanity.io is a seamless tool for flexible content management. It can be used to build simple to complex applications from the ground up.

    In this article, we’ll build a simple listing app with Sanity.io and React. Our global states will be managed with Redux and the application will be styled with styled-components.

    The fast evolution of digital platforms have placed serious limitations on traditional CMS like WordPress. These platforms are coupled, inflexible and are focused on the project, rather than the product. Thankfully, several headless CMS have been developed to tackle these challenges and many more.

    Unlike traditional CMS, headless CMS, which can be described as Software as a Service (SaaS), can be used to develop websites, mobile apps, digital displays, and many more. They can be used on limitless platforms. If you are looking for a CMS that is platform independent, developer-first, and offers cross platform support, you need not look farther from headless CMS.

    A headless CMS is simply a CMS without a head. The head here refers to the frontend or the presentation layer while the body refers to the backend or the content repository. This offers a lot of interesting benefits. For instance, it allows the developer to choose any frontend of his choice and you can also design the presentation layer as you want.

    There are lots of headless CMS out there, some of the most popular ones include Strapi, Contentful, Contentstack, Sanity, Butter CMS, Prismic, Storyblok, Directus, etc. These headless CMS are API-based and have their individual strong points. For instance, CMS like Sanity, Strapi, Contentful, and Storyblok are free for small projects.

    These headless CMS are based on different tech stacks as well. While Sanity.io is based on React.js, Storyblok is based on Vue.js. As a React developer, this is the major reason why I quickly picked interest in Sanity. However, being a headless CMS, each of these platforms can be plugged on any frontend, whether Angular, Vue or React.

    Each of these headless CMS has both free and paid plans which represent significant price jump. Although these paid plans offer more features, you wouldn’t want to pay all that much for a small to mid-sized project. Sanity tries to solve this problem by introducing pay-as-you-go options. With these options, you will be able to pay for what you use and avoid the price jump.

    Another reason why I choose Sanity.io is their GROQ language. For me, Sanity stands out from the crowd by offering this tool. Graphical-Relational Object Queries (GROQ) reduces development time, helps you get the content you need in the form you need it, and also helps the developer to create a document with a new content model without code changes.

    Moreover, developers are not constrained to the GROQ language. You can also use GraphQL or even the traditional axios and fetch in your React app to query the backend. Like most other headless CMS, Sanity has comprehensive documentation that contains helpful tips to build on the platform.

    Note: This article requires a basic understanding of React, Redux and CSS.

    Getting Started With Sanity.io

    To use Sanity in your machine, you’ll need to install the Sanity CLI tool. While this can be installed locally on your project, it is preferable to install it globally to make it accessible to any future applications.

    To do this, enter the following commands in your terminal.

    npm install -g @sanity/cli

    The -g flag in the above command enables global installation.

    Next, we need to initialize Sanity in our application. Although this can be installed as a separate project, it is usually preferable to install it within your frontend app (in this case React).

    In her blog, Kapehe explained in detail how to integrate Sanity with React. It will be helpful to go through the article before continuing with this tutorial.

    Enter the following commands to initialize Sanity in your React app.

    sanity init

    The sanity command becomes available to us when we installed the Sanity CLI tool. You can view a list of the available Sanity commands by typing sanity or sanity help in your terminal.

    When setting up or initializing your project, you’ll need to follow the prompts to customize it. You’ll also be required to create a dataset and you can even choose their custom dataset populated with data. For this listing app, we will be using Sanity’s custom sci-fi movies dataset. This will save us from entering the data ourselves.

    To view and edit your dataset, cd to the Sanity subdirectory in your terminal and enter sanity start. This usually runs on http://localhost:3333/. You may be required to login to access the interface (make sure you login with the same account you used when initializing the project). A screenshot of the environment is shown below.

    Sanity server overview
    An overview of the sanity server for the sci-fi movie dataset. (Large preview)

    Sanity-React Two-way Communication

    Sanity and React need to communicate with each other for a fully functional application.

    CORS Origins Setting In Sanity Manager

    We’ll first connect our React app to Sanity. To do this, login to https://manage.sanity.io/ and locate CORS origins under API Settings in the Settings tab. Here, you’ll need to hook your frontend origin to the Sanity backend. Our React app runs on http://localhost:3000/ by default, so we need to add that to the CORS.

    This is shown in the figure below.

    CORS origin settings
    Setting CORS origin in Sanity.io Manager. (Large preview)

    Connecting Sanity To React

    Sanity associates a project ID to every project you create. This ID is needed when connecting it to your frontend application. You can find the project ID in your Sanity Manager.

    The backend communicates with React using a library known as sanity client. You need to install this library in your Sanity project by entering the following commands.

    npm install @sanity/client

    Create a file sanitySetup.js (the filename does not matter), in your project src folder and enter the following React codes to set up a connection between Sanity and React.

    import sanityClient from "@sanity/client"
    export default sanityClient({
        projectId: PROJECT_ID,
        dataset: DATASET_NAME,
        useCdn: true
    });

    We passed our projectId, dataset name and a boolean useCdn to the instance of the sanity client imported from @sanity/client. This works the magic and connects our app to the backend.

    Now that we’ve completed the two-way connection, let’s jump right in to build our project.

    Setting Up And Connecting Redux To Our App

    We’ll need a few dependencies to work with Redux in our React app. Open up your terminal in your React environment and enter the following bash commands.

    npm install redux react-redux redux-thunk
    

    Redux is a global state management library that can be used with most frontend frameworks and libraries such as React. However, we need an intermediary tool react-redux to enable communication between our Redux store and our React application. Redux thunk will help us to return a function instead of an action object from Redux.

    While we could write the entire Redux workflow in one file, it is often neater and better to separate our concerns. For this, we will divide our workflow into three files namely, actions, reducers, and then the store. However, we also need a separate file to store the action types, also known as constants.

    Setting Up The Store

    The store is the most important file in Redux. It organizes and packages the states and ships them to our React application.

    Here is the initial setup of our Redux store needed to connect our Redux workflow.

    import { createStore, applyMiddleware } from "redux";
    import thunk from "redux-thunk";
    import reducers from "./reducers/";
    
    export default createStore(
      reducers,
      applyMiddleware(thunk)
    );
    

    The createStore function in this file takes three parameters: the reducer (required), the initial state and the enhancer (usually a middleware, in this case, thunk supplied through applyMiddleware). Our reducers will be stored in a reducers folder and we’ll combine and export them in an index.js file in the reducers folder. This is the file we imported in the code above. We’ll revisit this file later.

    Introduction To Sanity’s GROQ Language

    Sanity takes querying on JSON data a step further by introducing GROQ. GROQ stands for Graph-Relational Object Queries. According to Sanity.io, GROQ is a declarative query language designed to query collections of largely schema-less JSON documents.

    Sanity even provides the GROQ Playground to help developers become familiar with the language. However, to access the playground, you need to install sanity vision.
    Run sanity install @sanity/vision on your terminal to install it.

    GROQ has a similar syntax to GraphQL but it is more condensed and easier to read. Furthermore, unlike GraphQL, GROQ can be used to query JSON data.

    For instance, to retrieve every item in our movie document, we’ll use the following GROQ syntax.

    *[_type == "movie"]

    However, if we wish to retrieve only the _ids and crewMembers in our movie document. We need to specify those fields as follows.

    `*[_type == 'movie']{                                             
        _id,
        crewMembers
    }
    

    Here, we used * to tell GROQ that we want every document of _type movie. _type is an attribute under the movie collection. We can also return the type like we did the _id and crewMembers as follows:

    *[_type == 'movie']{                                             
        _id,
        _type,
        crewMembers
    }
    

    We’ll work more on GROQ by implementing it in our Redux actions but you can check Sanity.io’s documentation for GROQ to learn more about it. The GROQ query cheat sheet provides a lot of examples to help you master the query language.

    Setting Up Constants

    We need constants to track the action types at every stage of the Redux workflow. Constants help to determine the type of action dispatched at each point in time. For instance, we can track when the API is loading, fully loaded and when an error occurs.

    We don’t necessarily need to define constants in a separate file but for simplicity and clarity, this is usually the best practice in Redux.

    By convention, constants in Javascript are defined with uppercase. We’ll follow the best practices here to define our constants. Here is an example of a constant for denoting requests for moving movie fetching.

    export const MOVIE_FETCH_REQUEST = "MOVIE_FETCH_REQUEST";

    Here, we created a constant MOVIE_FETCH_REQUEST that denotes an action type of MOVIE_FETCH_REQUEST. This helps us to easily call this action type without using strings and avoid bugs. We also exported the constant to be available anywhere in our project.

    Similarly, we can create other constants for fetching action types denoting when the request succeeds or fails. A complete code for the movieConstants.js is given in the code below.

    Here we have defined several constants for fetching a movie or list of movies, sorting and fetching the most popular movies. Notice that we set constants to determine when the request is loading, successful and failed.

    Similarly, our personConstants.js file is given below:

    export const PERSONS_FETCH_REQUEST = "PERSONS_FETCH_REQUEST";
    export const PERSONS_FETCH_SUCCESS = "PERSONS_FETCH_SUCCESS";
    export const PERSONS_FETCH_FAIL = "PERSONS_FETCH_FAIL";
    
    export const PERSON_FETCH_REQUEST = "PERSON_FETCH_REQUEST";
    export const PERSON_FETCH_SUCCESS = "PERSON_FETCH_SUCCESS";
    export const PERSON_FETCH_FAIL = "PERSON_FETCH_FAIL";
    
    export const PERSONS_COUNT = "PERSONS_COUNT";

    Like the movieConstants.js, we set a list of constants for fetching a person or persons. We also set a constant for counting persons. The constants follow the convention described for movieConstants.js and we also exported them to be accessible to other parts of our application.

    Finally, we’ll implement light and dark mode in the app and so we have another constants file globalConstants.js. Let’s take a look at it.

    export const SET_LIGHT_THEME = "SET_LIGHT_THEME";
    export const SET_DARK_THEME = "SET_DARK_THEME";

    Here we set constants to determine when light or dark mode is dispatched. SET_LIGHT_THEME determines when the user switches to the light theme and SET_DARK_THEME determines when the dark theme is selected. We also exported our constants as shown.

    Setting Up The Actions

    By convention, our actions are stored in a separate folder. Actions are grouped according to their types. For instance, our movie actions are stored in movieActions.js while our person actions are stored in personActions.js file.

    We also have globalActions.js to take care of toggling the theme from light to dark mode.

    Let’s fetch all movies in moviesActions.js.

    import sanityAPI from "../../sanitySetup";
    import {
      MOVIES_FETCH_FAIL,
      MOVIES_FETCH_REQUEST,
      MOVIES_FETCH_SUCCESS  
    } from "../constants/movieConstants";
    
    const fetchAllMovies = () => async (dispatch) => {
      try {
        dispatch({
          type: MOVIES_FETCH_REQUEST
        });
        const data = await sanityAPI.fetch(
          `*[_type == 'movie']{                                            
              _id,
              "poster": poster.asset->url,
          } `
        );
        dispatch({
          type: MOVIES_FETCH_SUCCESS,
          payload: data
        });
      } catch (error) {
        dispatch({
          type: MOVIES_FETCH_FAIL,
          payload: error.message
        });
      }
    };

    Remember when we created the sanitySetup.js file to connect React to our Sanity backend? Here, we imported the setup to enable us to query our sanity backend using GROQ. We also imported a few constants exported from the movieConstants.js file in the constants folder.

    Next, we created the fetchAllMovies action function for fetching every movie in our collection. Most traditional React applications use axios or fetch to fetch data from the backend. But while we could use any of these here, we’re using Sanity’s GROQ. To enter the GROQ mode, we need to call sanityAPI.fetch() function as shown in the code above. Here, sanityAPI is the React-Sanity connection we set up earlier. This returns a Promise and so it has to be called asynchronously. We’ve used the async-await syntax here, but we can also use the .then syntax.

    Since we are using thunk in our application, we can return a function instead of an action object. However, we chose to pass the return statement in one line.

    const fetchAllMovies = () => async (dispatch) => {
      ...
    }

    Note that we can also write the function this way:

    const fetchAllMovies = () => {
      return async (dispatch)=>{
        ...
      }
    }

    In general, to fetch all movies, we first dispatched an action type that tracks when the request is still loading. We then used Sanity’s GROQ syntax to asynchronously query the movie document. We retrieved the _id and the poster url of the movie data. We then returned a payload containing the data gotten from the API.

    Similarly, we can retrieve movies by their _id, sort movies, and get the most popular movies.

    We can also fetch movies that match a particular person’s reference. We did this in the fetchMoviesByRef function.

    const fetchMoviesByRef = (ref) => async (dispatch) => {
      try {
        dispatch({
          type: MOVIES_REF_FETCH_REQUEST
        });
        const data = await sanityAPI.fetch(
          `*[_type == 'movie' 
                && (castMembers[person._ref match '${ref}'] || 
                    crewMembers[person._ref match '${ref}'])            
                ]{                                             
                    _id,                              
                    "poster" : poster.asset->url,
                    title
                } `
        );
        dispatch({
          type: MOVIES_REF_FETCH_SUCCESS,
          payload: data
        });
      } catch (error) {
        dispatch({
          type: MOVIES_REF_FETCH_FAIL,
          payload: error.message
        });
      }
    };

    This function takes an argument and checks if person._ref in either the castMembers or crewMembers matches the passed argument. We return the movie _id, poster url, and title alongside. We also dispatch an action of type MOVIES_REF_FETCH_SUCCESS, attaching a payload of the returned data, and if an error occurs, we dispatch an action of type MOVIE_REF_FETCH_FAIL, attaching a payload of the error message, thanks to the try-catch wrapper.

    In the fetchMovieById function, we used GROQ to retrieve a movie that matches a particular id passed to the function.

    The GROQ syntax for the function is shown below.

    const data = await sanityAPI.fetch(
          `*[_type == 'movie' && _id == '${id}']{                                               
                    _id,
                    "cast" :
                        castMembers[]{
                            "ref": person._ref,
                            characterName, 
                            "name": person->name,
                            "image": person->image.asset->url
                        }
                    ,
                    "crew" :
                        crewMembers[]{
                            "ref": person._ref,
                            department, 
                            job,
                            "name": person->name,
                            "image": person->image.asset->url
                        }
                    ,                
                    "overview":   {                    
                        "text": overview[0].children[0].text
                      },
                    popularity,
                    "poster" : poster.asset->url,
                    releaseDate,                                
                    title
                }[0]`
        );

    Like the fetchAllMovies action, we started by selecting all documents of type movie but we went further to select only those with an id supplied to the function. Since we intend to display a lot of details for the movie, we specified a bunch of attributes to retrieve.

    We retrieved the movie id and also a few attributes in the castMembers array namely ref, characterName, the person’s name, and the person’s image. We also changed the alias from castMembers to cast.

    Like the castMembers, we selected a few attributes from the crewMembers array, namely ref, department, job, the person’s name and the person’s image. we also changed the alias from crewMembers to crew.

    In the same way, we selected the overview text, popularity, movie’s poster url, movie’s release date and title.

    Sanity’s GROQ language also allows us to sort a document. To sort an item, we pass order next to a pipe operator.

    For instance, if we wish to sort movies by their releaseDate in ascending order, we could do the following.

    const data = await sanityAPI.fetch(
          `*[_type == 'movie']{                                            
              ...
          } | order(releaseDate, asc)`
        );
    

    We used this notion in the sortMoviesBy function to sort either by ascending or descending order.

    Let’s take a look at this function below.

    const sortMoviesBy = (item, type) => async (dispatch) => {
      try {
        dispatch({
          type: MOVIES_SORT_REQUEST
        });
        const data = await sanityAPI.fetch(
          `*[_type == 'movie']{                                
                    _id,                                               
                    "poster" : poster.asset->url,    
                    title
                    } | order( ${item} ${type})`
        );
        dispatch({
          type: MOVIES_SORT_SUCCESS,
          payload: data
        });
      } catch (error) {
        dispatch({
          type: MOVIES_SORT_FAIL,
          payload: error.message
        });
      }
    };

    We began by dispatching an action of type MOVIES_SORT_REQUEST to determine when the request is loading. We then used the GROQ syntax to sort and fetch data from the movie collection. The item to sort by is supplied in the variable item and the mode of sorting (ascending or descending) is supplied in the variable type. Consequently, we returned the id, poster url, and title. Once the data is returned, we dispatched an action of type MOVIES_SORT_SUCCESS and if it fails, we dispatch an action of type MOVIES_SORT_FAIL.

    A similar GROQ concept applies to the getMostPopular function. The GROQ syntax is shown below.

    const data = await sanityAPI.fetch(
          `
                *[_type == 'movie']{ 
                    _id,                              
                    "overview":   {                    
                        "text": overview[0].children[0].text
                    },                
                    "poster" : poster.asset->url,    
                    title 
                }| order(popularity desc) [0..2]`
        );

    The only difference here is that we sorted the movies by popularity in descending order and then selected only the first three. The items are returned in a zero-based index and so the first three items are items 0, 1 and 2. If we wish to retrieve the first ten items, we could pass [0..9] to the function.

    Here’s the complete code for the movie actions in the movieActions.js file.

    Setting Up The Reducers

    Reducers are one of the most important concepts in Redux. They take the previous state and determine the state changes.

    Typically, we’ll be using the switch statement to execute a condition for each action type. For instance, we can return loading when the action type denotes loading, and then the payload when it denotes success or error. It is expected to take in the initial state and the action as arguments.

    Our movieReducers.js file contains various reducers to match the actions defined in the movieActions.js file. However, each of the reducers has a similar syntax and structure. The only differences are the constants they call and the values they return.

    Let’s start by taking a look at the fetchAllMoviesReducer in the movieReducers.js file.

    import {
      MOVIES_FETCH_FAIL,
      MOVIES_FETCH_REQUEST,
      MOVIES_FETCH_SUCCESS,  
    } from "../constants/movieConstants";
    
    const fetchAllMoviesReducer = (state = {}, action) => {
      switch (action.type) {
        case MOVIES_FETCH_REQUEST:
          return {
            loading: true
          };
        case MOVIES_FETCH_SUCCESS:
          return {
            loading: false,
            movies: action.payload
          };
        case MOVIES_FETCH_FAIL:
          return {
            loading: false,
            error: action.payload
          };
        case MOVIES_FETCH_RESET:
          return {};
        default:
          return state;
      }
    };

    Like all reducers, the fetchAllMoviesReducer takes the initial state object (state) and the action object as arguments. We used the switch statement to check the action types at each point in time. If it corresponds to MOVIES_FETCH_REQUEST, we return loading as true to enable us to show a loading indicator to the user.

    If it corresponds to MOVIES_FETCH_SUCCESS, we turn off the loading indicator and then return the action payload in a variable movies. But if it is MOVIES_FETCH_FAIL, we also turn off the loading and then return the error. We also want the option to reset our movies. This will enable us to clear the states when we need to do so.

    We have the same structure for other reducers. The complete movieReducers.js is shown below.

    We also followed the exact same structure for personReducers.js. For instance, the fetchAllPersonsReducer function defines the states for fetching all persons in the database.

    This is given in the code below.

    import {
      PERSONS_FETCH_FAIL,
      PERSONS_FETCH_REQUEST,
      PERSONS_FETCH_SUCCESS,
    } from "../constants/personConstants";
    
    const fetchAllPersonsReducer = (state = {}, action) => {
      switch (action.type) {
        case PERSONS_FETCH_REQUEST:
          return {
            loading: true
          };
        case PERSONS_FETCH_SUCCESS:
          return {
            loading: false,
            persons: action.payload
          };
        case PERSONS_FETCH_FAIL:
          return {
            loading: false,
            error: action.payload
          };
        default:
          return state;
      }
    };
    

    Just like the fetchAllMoviesReducer, we defined fetchAllPersonsReducer with state and action as arguments. These are standard setup for Redux reducers. We then used the switch statement to check the action types and if it’s of type PERSONS_FETCH_REQUEST, we return loading as true. If it’s PERSONS_FETCH_SUCCESS, we switch off loading and return the payload, and if it’s PERSONS_FETCH_FAIL, we return the error.

    Combining Reducer

    Redux’s combineReducers function allows us to combine more than one reducer and pass it to the store. We’ll combine our movies and persons reducers in an index.js file within the reducers folder.

    Let’s take a look at it.

    import { combineReducers } from "redux";
    import {
      fetchAllMoviesReducer,
      fetchMovieByIdReducer,
      sortMoviesByReducer,
      getMostPopularReducer,
      fetchMoviesByRefReducer
    } from "./movieReducers";
    
    import {
      fetchAllPersonsReducer,
      fetchPersonByIdReducer,
      countPersonsReducer
    } from "./personReducers";
    
    import { toggleTheme } from "./globalReducers";
    
    export default combineReducers({
      fetchAllMoviesReducer,
      fetchMovieByIdReducer,
      fetchAllPersonsReducer,
      fetchPersonByIdReducer,
      sortMoviesByReducer,
      getMostPopularReducer,
      countPersonsReducer,
      fetchMoviesByRefReducer,
      toggleTheme
    });

    Here we imported all the reducers from the movies, persons, and global reducers file and passed them to combineReducers function. The combineReducers function takes an object which allows us to pass all our reducers. We can even add an alias to the arguments in the process.

    We’ll work on the globalReducers later.

    We can now pass the reducers in the Redux store.js file. This is shown below.

    import { createStore, applyMiddleware } from "redux";
    import thunk from "redux-thunk";
    import reducers from "./reducers/index";
    
    export default createStore(reducers, initialState, applyMiddleware(thunk));
    

    Having set up our Redux workflow, let’s set up our react application.

    Setting Up Our React Application

    Our react application will list movies and their corresponding cast and crewmembers. We will be using react-router-dom for routing and styled-components for styling the app. We’ll also use Material UI for icons and some UI components.

    Enter the following bash command to install the dependencies.

    npm install react-router-dom @material-ui/core @material-ui/icons query-string

    Here’s what we’ll be building.

    Connecting Redux To Our React App

    React-redux ships with a Provider function that allows us to connect our application to the Redux store. To do this, we have to pass an instance of the store to the Provider. We can do this either in our index.js or App.js file.

    Here’s our index.js file.

    import React from "react";
    import ReactDOM from "react-dom";
    import "./index.css";
    import App from "./App";
    import { Provider } from "react-redux";
    import store from "./redux/store";
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById("root")
    );

    Here, we imported Provider from react-redux and store from our Redux store. Then we wrapped our entire components tree with the Provider, passing the store to it.

    Next, we need react-router-dom for routing in our React application. react-router-dom comes with BrowserRouter, Switch and Route that can be used to define our path and routes.

    We do this in our App.js file. This is shown below.

    import React from "react";
    import Header from "./components/Header";
    import Footer from "./components/Footer";
    import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
    import MoviesList from "./pages/MoviesListPage";
    import PersonsList from "./pages/PersonsListPage";
    
    function App() {
    
      return (
          <Router>
            <main className="contentwrap">
              <Header />
              <Switch>
                <Route path="/persons/">
                  <PersonsList />
                </Route>
                <Route path="/" exact>
                  <MoviesList />
                </Route>
              </Switch>
            </main>
            <Footer />
          </Router>
      );
    }
    export default App;

    This is a standard setup for routing with react-router-dom. You can check it out in their documentation. We imported our components Header, Footer, PersonsList and MovieList. We then set up the react-router-dom by wrapping everything in Router and Switch.

    Since we want our pages to share the same header and footer, we had to pass the <Header /> and <Footer /> component before wrapping the structure with Switch. We also did a similar thing with the main element since we want it to wrap the entire application.

    We passed each component to the route using Route from react-router-dom.

    Defining Our Pages And Components

    Our application is organized in a structured way. Reusable components are stored in the components folder while Pages are stored in the pages folder.

    Our pages comprise movieListPage.js, moviePage.js, PersonListPage.js and PersonPage.js. The MovieListPage.js lists all the movies in our Sanity.io backend as well as the most popular movies.

    To list all the movies, we simply dispatch the fetchAllMovies action defined in our movieAction.js file. Since we need to fetch the list as soon as the page loads, we have to define it in the useEffect. This is shown below.

    import React, { useEffect } from "react";
    import { fetchAllMovies } from "../redux/actions/movieActions";
    import { useDispatch, useSelector } from "react-redux";
    
    const MoviesListPage = () => {
      const dispatch = useDispatch();
      useEffect(() => {    
          dispatch(fetchAllMovies());
      }, [dispatch]);
    
      const { loading, error, movies } = useSelector(
        (state) => state.fetchAllMoviesReducer
      );
      
      return (
        ...
      )
    };
    export default MoviesListPage;
    

    Thanks to the useDispatch and useSelector Hooks, we can dispatch Redux actions and select the appropriate states from the Redux store. Notice that the states loading, error and movies were defined in our Reducer functions and here selected them using the useSelector Hook from React Redux. These states namely loading, error and movies become available immediately we dispatched the fetchAllMovies() actions.

    Once we get the list of movies, we can display it in our application using the map function or however we wish.

    Here is the complete code for the moviesListPage.js file.

    We started by dispatching the getMostPopular movies action (this action selects the movies with the highest popularity) in the useEffect Hook. This allows us to retrieve the most popular movies as soon as the page loads. Additionally, we allowed users to sort movies by their releaseDate and popularity. This is handled by the sortMoviesBy action dispatched in the code above. Furthermore, we dispatched the fetchAllMovies depending on the query parameters.

    Also, we used the useSelector Hook to select the corresponding reducers for each of these actions. We selected the states for loading, error and movies for each of the reducers.

    After getting the movies from the reducers, we can now display them to the user. Here, we have used the ES6 map function to do this. We first displayed a loader whenever each of the movie states is loading and if there’s an error, we display the error message. Finally, if we get a movie, we display the movie image to the user using the map function. We wrapped the entire component in a MovieListContainer component.

    The <MovieListContainer> … </MovieListContainer> tag is a div defined using styled components. We’ll take a brief look at that soon.

    Styling Our App With Styled Components

    Styled components allow us to style our pages and components on an individual basis. It also offers some interesting features such as inheritance, Theming, passing of props, etc.

    Although we always want to style our pages on an individual basis, sometimes global styling may be desirable. Interestingly, styled-components provide a way to do that, thanks to the createGlobalStyle function.

    To use styled-components in our application, we need to install it. Open your terminal in your react project and enter the following bash command.

    npm install styled-components

    Having installed styled-components, Let’s get started with our global styles.

    Let’s create a separate folder in our src directory named styles. This will store all our styles. Let’s also create a globalStyles.js file within the styles folder. To create global style in styled-components, we need to import createGlobalStyle.

    import { createGlobalStyle } from "styled-components";

    We can then define our styles as follows:

    export const GlobalStyle = createGlobalStyle`
      ...
    `

    Styled components make use of the template literal to define props. Within this literal, we can write our traditional CSS codes.

    We also imported deviceWidth defined in a file named definition.js. The deviceWidth holds the definition of breakpoints for setting our media queries.

    import { deviceWidth } from "./definition";

    We set overflow to hidden to control the flow of our application.

    html, body{
            overflow-x: hidden;
    }

    We also defined the header style using the .header style selector.

    .header{
      z-index: 5;
      background-color: ${(props)=>props.theme.midDarkBlue}; 
      display:flex;
      align-items:center;
      padding: 0 20px;
      height:50px;
      justify-content:space-between;
      position:fixed;
      top:0;
      width:100%;
      @media ${deviceWidth.laptop_lg}
      {
        width:97%;
      }
      ...
    }

    Here, various styles such as the background color, z-index, padding, and lots of other traditional CSS properties are defined.

    We’ve used the styled-components props to set the background color. This allows us to set dynamic variables that can be passed from our component. Moreover, we also passed the theme’s variable to enable us to make the most of our theme toggling.

    Theming is possible here because we have wrapped our entire application with the ThemeProvider from styled-components. We’ll talk about this in a moment. Furthermore, we used the CSS flexbox to properly style our header and set the position to fixed to make sure it remains fixed with respect to the browser. We also defined the breakpoints to make the headers mobile friendly.

    Here is the complete code for our globalStyles.js file.

    import { createGlobalStyle } from "styled-components";
    import { deviceWidth } from "./definition";
    
    export const GlobalStyle = createGlobalStyle`
        html{
            overflow-x: hidden;
        }
        body{
            background-color: ${(props) => props.theme.lighter};        
            overflow-x: hidden;   
            min-height: 100vh;     
            display: grid;
            grid-template-rows: auto 1fr auto;
        }
        #root{        
            display: grid;
            flex-direction: column;   
        }    
        h1,h2,h3, label{
            font-family: 'Aclonica', sans-serif;        
        }
        h1, h2, h3, p, span:not(.MuiIconButton-label), 
        div:not(.PrivateRadioButtonIcon-root-8), div:not(.tryingthis){
            color: ${(props) => props.theme.bodyText}
        }
        
        p, span, div, input{
            font-family: 'Jost', sans-serif;       
        }
        
        .paginate button{
            color: ${(props) => props.theme.bodyText}
        }
        
        .header{
            z-index: 5;    
            background-color: ${(props) => props.theme.midDarkBlue};                
            display: flex;
            align-items: center;   
            padding: 0 20px;        
            height: 50px;
            justify-content: space-between;
            position: fixed;
            top: 0;
            width: 100%;
            @media ${deviceWidth.laptop_lg}{
                width: 97%;            
            }               
            
            @media ${deviceWidth.tablet}{
                width: 100%;
                justify-content: space-around;
            }
            a{
                text-decoration: none;
            }
            label{
                cursor: pointer;
                color: ${(props) => props.theme.goldish};
                font-size: 1.5rem;
            }        
            .hamburger{
                cursor: pointer;   
                color: ${(props) => props.theme.white};
                @media ${deviceWidth.desktop}{
                    display: none;
                }
                @media ${deviceWidth.tablet}{
                    display: block;                
                }
            }  
                     
        }    
        .mobileHeader{
            z-index: 5;        
            background-color: ${(props) =>
              props.theme.darkBlue};                    
            color: ${(props) => props.theme.white};
            display: grid;
            place-items: center;        
            
            width: 100%;      
            @media ${deviceWidth.tablet}{
                width: 100%;                   
            }                         
            
            height: calc(100% - 50px);                
            transition: all 0.5s ease-in-out; 
            position: fixed;        
            right: 0;
            top: 50px;
            .menuitems{
                display: flex;
                box-shadow: 0 0 5px ${(props) => props.theme.lightshadowtheme};           
                flex-direction: column;
                align-items: center;
                justify-content: space-around;                        
                height: 60%;            
                width: 40%;
                a{
                    display: flex;
                    flex-direction: column;
                    align-items:center;
                    cursor: pointer;
                    color: ${(props) => props.theme.white};
                    text-decoration: none;                
                    &:hover{
                        border-bottom: 2px solid ${(props) => props.theme.goldish};
                        .MuiSvgIcon-root{
                            color: ${(props) => props.theme.lightred}
                        }
                    }
                }
            }
        }
        
        footer{                
            min-height: 30px;        
            margin-top: auto;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;        
            font-size: 0.875rem;        
            background-color: ${(props) => props.theme.midDarkBlue};      
            color: ${(props) => props.theme.white};        
        }    
    `;
    

    Notice that we wrote pure CSS code within the literal but there are a few exceptions. Styled-components allows us to pass props. You can learn more about this in the documentation.

    Apart from defining global styles, we can define styles for individual pages.

    For instance, here is the style for the PersonListPage.js defined in PersonStyle.js in the styles folder.

    import styled from "styled-components";
    import { deviceWidth, colors } from "./definition";
    
    export const PersonsListContainer = styled.div`
      margin: 50px 80px;
      @media ${deviceWidth.tablet} {
        margin: 50px 10px;
      }
      a {
        text-decoration: none;
      }
      .top {
        display: flex;
        justify-content: flex-end;
        padding: 5px;
        .MuiSvgIcon-root {
          cursor: pointer;
          &:hover {
            color: ${colors.darkred};
          }
        }
      }
      .personslist {
        margin-top: 20px;
        display: grid;
        place-items: center;
        grid-template-columns: repeat(5, 1fr);
        @media ${deviceWidth.laptop} {
          grid-template-columns: repeat(4, 1fr);
        }
        @media ${deviceWidth.tablet} {
          grid-template-columns: repeat(3, 1fr);
        }
        @media ${deviceWidth.tablet_md} {
          grid-template-columns: repeat(2, 1fr);
        }
        @media ${deviceWidth.mobile_lg} {
          grid-template-columns: repeat(1, 1fr);
        }
        grid-gap: 30px;
        .person {
          width: 200px;
          position: relative;
          img {
            width: 100%;
          }
          .content {
            position: absolute;
            bottom: 0;
            left: 8px;
            border-right: 2px solid ${colors.goldish};
            border-left: 2px solid ${colors.goldish};
            border-radius: 10px;
            width: 80%;
            margin: 20px auto;
            padding: 8px 10px;
            background-color: ${colors.transparentWhite};
            color: ${colors.darkBlue};
            h2 {
              font-size: 1.2rem;
            }
          }
        }
      }
    `;
    

    We first imported styled from styled-components and deviceWidth from the definition file. We then defined PersonsListContainer as a div to hold our styles. Using media queries and the established breakpoints, we made the page mobile-friendly by setting various breakpoints.

    Here, we have used only the standard browser breakpoints for small, large and very large screens. We also made the most of the CSS flexbox and grid to properly style and display our content on the page.

    To use this style in our PersonListPage.js file, we simply imported it and added it to our page as follows.

    import React from "react";
    
    const PersonsListPage = () => {
      return (
        <PersonsListContainer>
          ...
        </PersonsListContainer>
      );
    };
    export default PersonsListPage;
    

    The wrapper will output a div because we defined it as a div in our styles.

    Adding Themes And Wrapping It Up

    It’s always a cool feature to add themes to our application. For this, we need the following:

    • Our custom themes defined in a separate file (in our case definition.js file).
    • The logic defined in our Redux actions and reducers.
    • Calling our theme in our application and passing it through the component tree.

    Let’s check this out.

    Here is our theme object in the definition.js file.

    export const theme = {
      light: {
        dark: "#0B0C10",
        darkBlue: "#253858",
        midDarkBlue: "#42526e",
        lightBlue: "#0065ff",
        normal: "#dcdcdd",
        lighter: "#F4F5F7",
        white: "#FFFFFF",
        darkred: "#E85A4F",
        lightred: "#E98074",
        goldish: "#FFC400",
        bodyText: "#0B0C10",
        lightshadowtheme: "rgba(0, 0, 0, 0.1)"
      },
      dark: {
        dark: "white",
        darkBlue: "#06090F",
        midDarkBlue: "#161B22",
        normal: "#dcdcdd",
        lighter: "#06090F",
        white: "white",
        darkred: "#E85A4F",
        lightred: "#E98074",
        goldish: "#FFC400",
        bodyText: "white",
        lightshadowtheme: "rgba(255, 255, 255, 0.9)"
      }
    };
    

    We have added various color properties for the light and dark themes. The colors are carefully chosen to enable visibility both in light and dark mode. You can define your themes as you want. This is not a hard and fast rule.

    Next, let’s add the functionality to Redux.

    We have created globalActions.js in our Redux actions folder and added the following codes.

    import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants";
    import { theme } from "../../styles/definition";
    
    export const switchToLightTheme = () => (dispatch) => {
      dispatch({
        type: SET_LIGHT_THEME,
        payload: theme.light
      });
      localStorage.setItem("theme", JSON.stringify(theme.light));
      localStorage.setItem("light", JSON.stringify(true));
    };
    
    export const switchToDarkTheme = () => (dispatch) => {
      dispatch({
        type: SET_DARK_THEME,
        payload: theme.dark
      });
      localStorage.setItem("theme", JSON.stringify(theme.dark));
      localStorage.setItem("light", JSON.stringify(false));
    };

    Here, we simply imported our defined themes. Dispatched the corresponding actions, passing the payload of the themes we needed. The payload results are stored in the local storage using the same keys for both light and dark themes. This enables us to persist the states in the browser.

    We also need to define our reducer for the themes.

    import { SET_DARK_THEME, SET_LIGHT_THEME } from "../constants/globalConstants";
    
    export const toggleTheme = (state = {}, action) => {
      switch (action.type) {
        case SET_LIGHT_THEME:
          return {
            theme: action.payload,
            light: true
          };
        case SET_DARK_THEME:
          return {
            theme: action.payload,
            light: false
          };
        default:
          return state;
      }
    };

    This is very similar to what we’ve been doing. We used the switch statement to check the type of action and then returned the appropriate payload. We also returned a state light that determines whether light or dark theme is selected by the user. We’ll use this in our components.

    We also need to add it to our root reducer and store. Here is the complete code for our store.js.

    import { createStore, applyMiddleware } from "redux";
    import thunk from "redux-thunk";
    import { theme as initialTheme } from "../styles/definition";
    import reducers from "./reducers/index";
    
    const theme = localStorage.getItem("theme")
      ? JSON.parse(localStorage.getItem("theme"))
      : initialTheme.light;
    
    const light = localStorage.getItem("light")
      ? JSON.parse(localStorage.getItem("light"))
      : true;
    
    const initialState = {
      toggleTheme: { light, theme }
    };
    export default createStore(reducers, initialState, applyMiddleware(thunk));

    Since we needed to persist the theme when the user refreshes, we had to get it from the local storage using localStorage.getItem() and pass it to our initial state.

    Adding The Functionality To Our React Application

    Styled components provide us with ThemeProvider that allows us to pass themes through our application. We can modify our App.js file to add this functionality.

    Let’s take a look at it.

    import React from "react";
    import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
    import { useSelector } from "react-redux";
    import { ThemeProvider } from "styled-components";
    
    function App() {
      const { theme } = useSelector((state) => state.toggleTheme);
      let Theme = theme ? theme : {};
      return (
        <ThemeProvider theme={Theme}>
          <Router>
            ...
          </Router>
        </ThemeProvider>
      );
    }
    export default App;

    By passing themes through the ThemeProvider, we can easily use the theme props in our styles.

    For instance, we can set the color to our bodyText custom color as follows.

    color: ${(props) => props.theme.bodyText};

    We can use the custom themes anywhere we need color in our application.

    For example, to define border-bottom, we do the following.

    border-bottom: 2px solid ${(props) => props.theme.goldish};

    Conclusion

    We began by delving into Sanity.io, setting it up and connecting it to our React application. Then we set up Redux and used the GROQ language to query our API. We saw how to connect and use Redux to our React app using react-redux, use styled-components and theming.

    However, we only scratched the surface on what is possible with these technologies. I encourage you to go through the code samples in my GitHub repo and try your hands on a completely different project using these technologies to learn and master them.

    Resources

    Smashing Editorial
    (ks, vf, yk, il)

    Source link

    web design

    From Design To Developer-Friendly React Code In Minutes With Anima — Smashing Magazine

    01/26/2021

    In this article, we’ll learn how to turn our static designs into a live, code-based prototype with real fields, forms, maps, and animations, and in turn transform this prototype into React code — all integrated in one tool.

    The promise of seamless design to code translation goes back to the early WYSIWYG page builders. Despite the admirable goal, their biggest flaw (among many) was the horrible code that they generated. Skepticism remains to this day and whenever this idea reappears, the biggest concerns are always related to the quality and maintainability of the code.

    This is about to change as new products have made great leaps in the right direction. Their ultimate goal is to automate the design to code process, but not at the cost of code quality. One of these products, Anima, is trying to finally bridge the gap by providing a fully-fledged design to development platform.

    What’s Anima?

    Anima is a design-to-development tool. It aims to turn the design handoff process into a continuous collaboration. Designers can use Anima to create fully responsive prototypes that look and work exactly like the finished product (no coding required). Developers in turn can take these designs and export them into developer-friendly React/HTML code. Instead of coding UI from scratch, they are free to focus on logic and architecture.

    It does that with the help of a plugin that connects directly to your design tool and allows you to configure designs and sync them to Anima’s web platform. That’s where the rest of the team can access the prototype, discuss it, and pick useful specs or assets. Aside from the collaboration functionality, it gives developers a headstart thanks to the generated code.

    This could make a big difference in the traditional back and forth dance that goes between designers and developers. It keeps everything in one place, in sync, and allows both sides to make changes using either code or design tools.

    In this article, we’ll learn how to turn our static designs into a live, code-based prototype with real fields, forms, maps, and animations, and in turn transform this prototype into React code — all integrated in one tool.

    Installing The Plugin And Setting Up A Project

    Getting started with Anima is simple. You first need to create an account and then install the plugin. While I’ll be using Figma for this walkthrough, Anima supports all of the major design tools: Sketch, Figma and Adobe XD.

    Installing the plugin
    Anima plugin is available for Sketch, Figma and Adobe XD. (Large preview)

    Once this is done, make sure you create a project on Anima’s platform — that’s where our designs will appear when we sync them.

    Starting a project
    Creating a new project. (Large preview)

    The plugin itself is separated into three main sections, each with a list of options. Most of what we’ll be doing is simply selecting one of those options and then applying a specific layer or frame in Figma.

    Plugin’s interface
    With options for choosing smart layers, flow or layout options. (Large preview)

    Creating A Responsive Prototype

    For the purpose of the article, we have designed an onboarding experience that will be transformed into an interactive prototype. So far we have prepared screens for the three most common breakpoints and we have linked them together using Figma’s prototyping features.

    Design previews
    Screens for the three most common breakpoints, linked via Figma’s prototyping features. (Large preview)

    One of the interesting things we can achieve with Anima is making prototypes that fit all screen sizes. Traditional prototypes made of clickable images are static and often fail under different screen sizes.

    To do that, click on “Breakpoints” option and Anima will ask you for the frames that you want to connect. Select all of the frames to add them as breakpoints. Then confirm your selection by clicking on “Done”.

    Selecting the frames and adding them as breakpoints

    Once you are ready, click on “Preview in browser” to see the result. That’s when Anima will convert your designs into code.

    The first thing you’ll notice is that the prototype is now transformed into HTML and CSS. All the content is selectable and reflows as the screen is resized. This is most visible when you select the “Responsive” mode in the prototype previewer and play with different screen sizes.

    To achieve smoother transitions, it’s important to use Figma’s constraint features when designing your components. Make sure to also check the box “Use Figma Constraints” in the “Layout” section of the plugin.

    Bring Your Designs To Life With Smart Layers

    We can take things a little bit further. Since Anima converts designs into code, the possibilities are endless for the things we can add to make our prototype more realistic.

    Animations and hover effects would be a great way to make the prototype more alive and to impress stakeholders. Anima offers a variety of options that can be applied to any layer or component. In our case, we’ll select the headline layer, then choose the “Entrance animation” and “Fade In”. In the delay field, we’ll add 0.5.

    For each field, we’ll add a glow effect on hover. Select the field layer, then “Hover effect” and choose “Glow”. Repeat the same for the button.

    Adding hovers and entrance animations with Anima (Illustration by Radostina Georgieva)

    Now that we have applied all the changes, we can see that the prototype starts to feel like a real product.

    A preview of the hovers and animations with Anima (Illustration by Radostina Georgieva)

    One of the unique features that Anima offers is the ability to add live fields and forms to prototypes. Since we are designing an onboarding experience, this will actually be really useful for us. Data entry is one of the biggest churn points in any product experience and it’s really hard to test out ideas without taking it into account.

    Similar to how we added the previous effects, we now select the field component and choose “Text field”. From there, we’ll have to choose the type of field that we need. If we choose a password field, for example, input will be hidden and Anima will add a show/hide functionality to the field.

    Adding text field effect with Anima

    As you can see, fields now work as intended. It’s also possible to gather all the data collected from those fields in a spreadsheet. Select the “Continue” button and then click on the “Submit Button” option in Anima. This will open an additional dialog, where we need to check the box “Add to Spreadsheet” and select redirect destinations in case of success or failure.

    Previewing text input and submission

    Next, we’ll add a Lottie animation for our success screen as it will be a great way to make the experience a bit more engaging. For that, we need to add a placeholder layer in the place of the animation, then select it and choose the “Video / GIF / Lottie” option in the plugin.

    Then we’ll paste the URL of our Lottie animation and check the boxes of “Autoplay” and “No controls”. In our case, we don’t want to have any video player controls, since this is a success animation.

    Apply the changes and open the preview mode to see the results. As you can see, when we fill out the fields and submit the form, we get redirected to our success page, with a looping animation.

    Previewing the Lottie animation

    Share Designs With The Rest Of The Team

    Up until that point, we were working on a draft that was visible only to us. Now it’s time to share it with the rest of the team. The way to do this in the app is by clicking on “Preview in browser”, check how it looks and, if you’re satisfied, continue with “Sync”.

    Everyone invited to the project will now have access to the designs and will be able to preview, leave comments and inpsect code.

    Developers Can Get Reusable React Code

    As mentioned earlier, as developers, we are usually skeptical of tools that generate code, mostly because writing something from scratch is always faster than refactoring something that was poorly written. To avoid this, Anima has adopted some best practices to keep the code clean, reuseable, and concise.

    Inspecting an element and switching between HTML and React

    When we switch to the “Code” mode, we can hover and inspect elements of our design. Whenever we select an element, we’ll see the generated code underneath. The default view is React, but we can also switch to HTML and CSS. We can also adjust preferences in the syntax and naming conventions.

    The classes reuse the names of the layers within your design tool, but both designers and developers can rename the layers, too. Still, it’s important to agree on unified naming conventions that would be clear and straightforward to both designers and developers.

    Even if we have left some layers unnamed, developers can actually override them and make changes when necessary. This experience reminds of the Chrome’s Inspect element feature, and all the changes are saved and synced with the project.

    If you are using Vue or Angular, it’s expected that Anima will start supporting these frameworks in the near future as well.

    Looking Forward

    As we can see, the gap between design and code keeps bridging. For those who write code, using such a tool is very practical as it can reduce a lot of repetitive work in front-end. For those who design, it allows prototyping, collaboration and syncing that would be difficult to achieve with sending static images back-and-forth.

    What’s already certain is that Anima eliminates a lot of wasteful activities in the hand-off process and allows both designers and developers to focus on what matters – building better products. We are looking forward to see what will be coming up next in Anima.

    Smashing Editorial
    (vf, il)

    Source link

    web design

    Using Grommet In React Applications — Smashing Magazine

    01/18/2021

    About The Author

    Fortune Ikechi is a Frontend Engineer based in Rivers State Nigeria. He is a student of the University of Port-Harcourt. He is passionate about community and …
    More about
    Fortune

    In this tutorial, we’re going to learn how to use Grommet as a UI library for React applications. We’ll use Grommet as a UI library of choice to create a pricing component, this would help us have a better understanding of how to use Grommet.

    Over the years, the React ecosystem has grown with the invention of libraries that help the development of React applications. In this tutorial, we are going to learn to use Grommet for developing responsive, accessible, and mobile-first components for React applications. We’ll take a closer look at its core concepts, some of its use cases, and build a simple example. It’s important to note that Grommet is open-source with 6.9k stars on GitHub.

    This tutorial will be beneficial to readers who are interested in developing responsive components in their React application using Grommet. This article requires a basic understanding of React and Styled-components.

    What Is Grommet?

    Grommet is a React component library that boasts of responsive and accessible mobile-first code components. It does this through its components — which are the building blocks for the library. They include Layouts, Types, Colors, Controls, Inputs, Visualizations Media and utilities. All grommet components are inbuilt with accessibility and responsiveness in mind.

    Grommet provides support for W3C’s spec which makes it score a huge point in terms of accessibility. It also provides powerful themes and tools that allow you to customize your color, type, component elements and layout needs according to your project needs.

    Some popular alternatives to Grommet include tailwindcss and styled components, although very popular among developers, each framework differ in approach in building applications. Grommet is mobile-first, accessible, responsive and themes out of the box and has support for W3C for easy creation of React applications while Tailwind CSS is a highly customizable and utility framework that allows developers to build applications without the restrictions of CSS such as its cascading rules. Styled-components aim to help developers write reusable React components by allowing us to write CSS code in our JavaScript using object literals and it also uses components as low-level styling construct.

    In our project, we will be using Grommet in our projects due to its customizable components, accessibility, and theme properties which we’d need as we go forward in this tutorial.

    Using Grommet Components

    Grommet like so many other component libraries comes pre-built with some components for layouts and themes such as Box, Card and Header components. To use first you’d need to install the grommet package using NPM or yarn, like the code block below.

    npm i grommet styled-components

    Or:

    yarn add grommet styled-components

    From the above, you can see that we also installed styled-components. This is because Grommet uses styled-components for customizing styles in components; it’s advisable to install styled-components in your projects.

    To use a Grommet component in a React project, you need to import grommet. Let’s build a card component below to explain:

    import React from 'react';
    import { Grommet, Card } from 'grommet';
    
    export default function GrommetExample() {
      return (
         <Card>
            <CardBody pad="medium">Body</CardBody>
              <Button
                icon={<Icons.Favorite color="red" />}
                  hoverIndicator
                />
            </Card>
          );
        }

    In the code block above, first imported Grommet and the Card component from grommet package into your file, next we wrapped our component using the Card component we’ve imported. Styles can be added to a Grommet component as objects like we did to the Button or they can be styled using styled-components.

    Let’s see more examples of Grommet components by looking at Form components.

    Why Grommet?

    Grommet’s primary purpose is to improve the experience of developers and make for a faster way of building React applications with its mobile-first, accessible, and responsive components. Grommet seamlessly aligns a design and a developer workflow to create a seamless experience, making it very easy for anyone to get started with.

    Grommet also provides support for screen readers out of the box, theme variants such as dark-mode are gotten from grommet out of the box and they can be set up using the themeMode prop in a React application, like below.

    import React from "react";
    import { Grommet, Box, Button, Heading, dark } from "grommet";
    import { grommet } from "grommet";
    const App = () => {
      const [darkMode, setDarkMode] = React.useState(false);
      return (
        <Grommet full theme={grommet} themeMode={darkMode ? "dark" : "light"}>
          <Box pad="large">
            <Heading level="1">Grommet Darkmode toggle</Heading>
            <Button
              label="Toggle Theme"
              primary
              alignSelf="center"
              margin="large"
              onClick={() => setDarkMode(!darkMode)}
            />
          </Box>
        </Grommet>
      );
    };
    export default App;

    In the code block above, we are using the themeMode property to add a dark mode. Using a ternary operator, we check if the page is on dark mode, we can toggle it to light mode, next we added a button for toggling between the light and dark mode on our application, you can check here for a demo on Codesandbox.

    Grommet can also exist with other frameworks and doesn’t add a global style that will affect existing components in your React application, functions and styles can be interpolated into an object literal for styles. Grommet also features Layout components, which features some CSS properties such as flexbox, it also takes in all flexbox properties as props.

    Grommet features a big library of SVG icons that are accessible using the <Icon /> component, unlike many other frameworks. Grommet features components for data visualization such as bar charts, maps and even progress trackers.

    Several firms use Grommet today to create real-world applications, including Netflix, IBM, Sony, Samsung, Shopify, GitHub and Twilio.

    Building A Pricing Component With Grommet

    Now we know the basics and core concepts of Grommet, we are going to create a pricing component using Grommet components, it should feature components such as Card, Box and Buttons from the Grommet library.

    Without further ado, let’s start!

    Setting Up Your Environment

    First, let’s create a bare React application, write the code block below on your terminal.

    create-react-app grommet-app

    The above code will create a bare React application using the create-react-app package. Move into the project directory.

    cd grommet-app

    Next is to install the dependencies we’d need in our project.

    yarn add grommet styled-components

    If you’ve done this, then start the project server using the command below.

    yarn start

    For this project, we’d need a single component for our cards and style with styled-components.

    Let’s create the first card below

    import React from "react";
    import styled from "styled-components";
    
    export default function GrommetCard() {
      return (
        <>
           <CardWrapper>
            <Card left>
              <Div>
                <Div>
                  <CardContent>
                    <small>Basic</small>
                    <h1>$588</h1>
                  </CardContent>
                  <CardContent>
                    <p>500 GB storage</p>
                  </CardContent>
                  <CardContent>
                    <p>2 Users Allowed</p>
                  </CardContent>
                  <CardContent>
                    <p>Send Up To 3 GB</p>
                  </CardContent>
                </Div>
                <CardButton secondary>LEARN MORE</CardButton>
              </Div>
            </Card>
       </CardWrapper>
        </>
      );
    }

    In the code block above, we are using the component CardWrapper to wrap all of our Card components, next we added a new component, CardContent which is used to wrap all of our content in each card component. The CardButton component is a button component that is used on cards on Grommet.

    Next, let’s create styles for our application using styled-components. Write the file below:

    const primaryGradient = "linear-gradient(hsl(236, 72%, 79%), hsl(237, 63%, 64%))";
    
    const CardWrapper = styled.div`
      display: flex;
      justify-content: center;
      align-items: center;
      height: max-content;
      margin: 20px;
      @media all and (max-width: 1240px) {
        flex-direction: column;
      }
    `;

    In the above, we defined a style object for our CardWrapper in our application. Let’s add style objects for our Card component above.

    const Card = styled.div`
      min-width: 380px;
      box-shadow: 3px -2px 19px 0px rgba(50, 50, 50, 0.51);
      border-radius: ${(props) => (props.left ? " 6px 0 0 6px" : props.right ? "0 6px 6px 0" : "6px")};
      background: ${(props) => (props.secondary === undefined ? "#fff" : primaryGradient)};
      padding: 25px 20px;
      height: ${(props) => (props.center ? "520px" : "480px")};
      display: flex;
      justify-content: center;
      align-items: center;
      @media all and (max-width: 1240px) {
        margin-bottom: 20px;
        border-radius: 6px;
        height: 480px;
      }
      @media all and (max-width: 420px) {
        min-width: 90%;
      }
    `;

    Let’s add more styles to our components.

    const CardButton = styled.div`
      min-width: 100%;
      padding: 10px 15px;
      min-height: 50px;
      box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2), 0px 0px 2px rgba(0, 0, 0, 0.2);
      color: ${(props) => (props.secondary !== undefined ? "#fff" : "#7c7ee3")};
      background: ${(props) => (props.secondary === undefined ? "#fff" : primaryGradient)};
      text-align: center;
      margin-top: 25px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-weight: 600;
      font-size: 16px;
      border-radius: 6px;
    `;
    const CardContent = styled.div`
      width: 100%;
      color: ${(props) => (props.secondary !== undefined ? "#fff" : "#000")};
      padding-bottom: 10px;
      margin-bottom: 10px;
      border-bottom: 1.3px solid #eee;
      text-align: center;
    `;
    const Div = styled.div`
      min-width: 100%;
    `;

    Once we’ve done all this, our project should look similar to the image below.

    A Grommet card
    A Grommet card. (Large preview)

    We need to add more cards to our component using the code block below.

     <Card center secondary>
             <Div>
                <Div>
                  <CardContent secondary>
                    <small>Premium</small>
                    <h1>$788</h1>
                  </CardContent>
                  <CardContent secondary>
                    <p>75 GB storage</p>
                  </CardContent>
                  <CardContent secondary>
                    <p>4 Users Allowed</p>
                  </CardContent>
                  <CardContent secondary>
                    <p>Send Up To 5 GB</p>
                  </CardContent>
                </Div>
                <CardButton>LEARN MORE</CardButton>
              </Div>
            </Card>
            
           <Card right>
              <Div>
                <Div>
                  <CardContent>
                    <small>PRO</small>
                    <h1>$1000</h1>
                  </CardContent>
                  <CardContent>
                    <p>1TB storage</p>
                  </CardContent>
                  <CardContent>
                    <p>Unlimited Users Allowed</p>
                  </CardContent>
                  <CardContent>
                    <p>Send Up To 10 GB</p>
                  </CardContent>
                </Div>
                <CardButton secondary>LEARN MORE</CardButton>
              </Div>
            </Card>
          </CardWrapper>
        </>
      );
    }
    

    Here, we created two more card components, adding our own custom components with styled-components and used the style objects we defined above to wrap our Grommet components and improve styling.

    Our final price card application should look like the image below.

    Grommet price card application
    Grommet price card application. (Large preview)

    Using Grommet In Production (Building List App)

    To see an example of what it’d look like using Grommet in another application, we are going to build a simple app that will allow a user to add, view and delete list items. We will be using in-built React Context API to manage the state of the application, Grommet for our UI components and styled-components for styling our application.

    Again, let’s initialize a react app using the command below.

    create-react-app list-app

    cd into the project directory

    cd list-app
    yarn add grommet grommet-controls grommet-icons styled-components

    In the above code block, we installed:

    grommet Our UI component library
    grommet-controls, grommet-icons Icons and controls packages we need to install to work with Grommet
    styled-components For utilizing tagged literals for styling react components and grommet

    Building The App Context

    In the application we need to share the user’s data across multiple components, to achieve that we would make use of Context API. With this, we can create an App Context that would hold the lists and logic for our application. You can check out this article to learn more about Context API.

    To create our app context, first create a folder called context in the src directory of our application, next create a file called AppContext.js this will be the file for all our app context, let’s do that in the code block below:

    import React, { createContext, useState } from 'react';
    export const Context = createContext();
    const AppContext = ({children}) => {
      const [lists, setLists] = useState([]);
      const removeList = item => {
        let newLists = [...lists];
        
        lists.map((list, id) => {
          return list === item && newLists.splice(id, 1);
        });
        setLists(newLists);
      }

    In the code block above, we imported the context API hook createContext and the useState hook all from React, using the useState component, we created a central state for our application, this was done so that the component can act as a Context Provider for other components in our application. Next, we created a new variable named removeList that takes in an item as a parameter, using the spread operator we are spreading what’s in the state and splicing out the object that’s equal to the item we want to remove.

    Next, we will use the logic above to create methods for adding and deleting list items in our application, we do that in the code block below:

    return (
        <Context.Provider value={{
          lists,
          addToLists: (newItem) => setLists([...lists, newItem]),
          deleteFromList: (item) => removeList(item)
        }}>
          {children}
        </Context.Provider>
      )
    }
    export default AppContext;

    Here, we are returning the Context.Provider and accepting children props, we are doing this so that other component will be able to access the properties we pass in the value prop, we initialized the lists object to take in our lists, the addToList method takes in a newItem parameter to add new lists to our application state and the deleteFromList removes or deletes an item from the list store.

    Building The List Component

    In this section, we are going to build our List component using Grommet for our UI components and styled-components to style some parts of our UI. First, create a components folder inside our application src directory, then inside the components folder, create a new file List.js and inside it, write the code below.

    import React from "react";
    import styled from "styled-components";
    import { Card, CardBody, Box, Text, Button } from "grommet";
    function List(props) {
      return (
        <StyledDiv>
          <Card>
            <CardBody className="card_body">
              <Box direction="row" className="item_box">
                <Text className="text">{props.list}</Text>
                <Box className="button_box">
                  <Button
                    onClick={props.deleteList.bind(this, props.list)}
                    className="button"
                  >
                    Delete
                  </Button>
                </Box>
              </Box>
            </CardBody>
          </Card>
        </StyledDiv>
      );
    }
    export default List;

    In the code above, we first imported components Card, CardBody, Box, Text and Button from grommet, next we created a List component to take in props, using Grommet components we created a card component with a delete button that will be automatically added to a list. Next is to style our component below:

    const StyledDiv = styled.div`
      .button {
        background-color: #8b0000;
        color: white;
        padding: 10px;
        border-radius: 5px;
      }
      .card_body {
        padding: 20px;
        margin-top: 20px;
      }
      .item_box {
        justify-content: space-between;
      }
      .text {
        margin-top: auto;
        margin-bottom: auto;
      }
    `;
    

    Once we do the above, our component should look like the image below.

    List component
    List component. (Large preview)

    Building The List Display Component

    This component displays all the lists we’ve added and also automatically generates a delete button as soon as a new list is added.

    import React from "react";
    import List from "./List";
    import { Context } from '../context/AppContext';
    function ListDisplay() {
      return (
        <Context.Consumer>
          {(context) => (
            <div className="container">
              {context.lists.length ? 
                context.lists.map((list, id) => (
                  <List key={id} list={list} deleteList={context.deleteFromList} />
                )) : null
              }
            </div>
          )}
        </Context.Consumer>
      );
    }
    export default ListDisplay;

    In this component, we created a function ListDisplay and wrapped it using the Context.Consumer from our appContext component, next using a div for our container tag, we destructured the list and deleteList methods from the app context, by doing this we can be able to pass them as props. Next, we map through the lists to return a new list, which we can use in building a single list by passing the returned object as props to the List component.

    Our component should look like this with lists added:

    list display component
    List display component. (Large preview)

    This component will be the bulk of our application, here we will wrao our component using the Context.Consumer and similar to our other components, we will be styling with styled components for styling. Let’s build this component below.

    import React, { useState } from "react";
    import { Heading, Form, TextInput, Button } from "grommet";
    import styled from "styled-components";
    import { Context } from '../context/AppContext';
    function Navbar() {
      const [value, setValue] = useState("");
      return (
        <Context.Consumer>
          {store => (
            <StyledDiv className="container">
              <Heading className="title">Grommet List App</Heading>
              <Form onSubmit={() => store.addToLists(value)} className="form-group">
                <TextInput
                  className="form"
                  value={value}
                  type="text"
                  onChange={(e) => setValue(e.target.value)}
                  placeholder="Enter item"
                />
                <Button type='submit' className="button">Add to List</Button>
              </Form>
            </StyledDiv>
          )}
        </Context.Consumer>
      );
    }
    const StyledDiv = styled.div`
      .button {
        margin-top: 10px;
        background-color: purple;
        color: white;
        padding: 10px;
        border-radius: 5px;
      }
    `;
    export default Navbar;
    

    First, in order to access the properties in the application context provider, we wrapped our component in a Context.Consumer component. Next, we added a Heading tag from Grommet, and then we created an input form for adding our lists by using the method addToList which takes in a value parameter (in our case the value is the user’s input). Last but not least, we added a Submit button to handle the form submit.

    Once done correctly, our app should look like this:

    grommet list app
    Grommet list app. (Large preview)

    Conclusion

    In this article, we learned about Grommet, a component library with responsiveness and accessibility in mind. We also went through the process of creating a pricing component application using Grommet and a list application. Have fun using Grommet for your component and UI needs for your next React application. The code for the Grommet list application can be found on Codesandbox and the pricing component can be found here.

    Resources

    Smashing Editorial
    (ks, vf, yk, il)

    Source link

    web design

    Integrating A Dialogflow Agent Into A React Application — Smashing Magazine

    01/14/2021

    About The Author

    Nwani Victory works as a Frontend Engineer at Liferithms.inc from Lagos, Nigeria. After office hours, he doubles as a Cloud Engineer seeking ways to make Cloud …
    More about
    Nwani

    When it comes to building a conversational chat assistant that could be used at a small or enterprise level, Dialogflow would most likely be one of the first options that would show up in your search list — and why wouldn’t it? It offers several features such as the ability to process audio and text inputs, provide dynamic responses using custom webhooks, connect to millions of Google-enabled devices by using the Google assistant, and so much more. But apart from its console that is provided to design and manage an Agent, how can we create a chat assistant that can be used within our built web applications, too?

    Dialogflow is a platform that simplifies the process of creating and designing a natural language processing conversational chat assistant which can process voice or text input when being used either from the Dialogflow console or from an integrated web application.

    Although the integrated Dialogflow Agent is briefly explained in this article, it is expected that you have an understanding of Node.js and Dialogflow. If you are learning about Dialogflow for the first time, this article gives a clear explanation of what Dialogflow is and its concepts.

    This article is a guide on how a built a Dialogflow agent with voice and chat support that can be integrated into a web application with the help of an Express.js back-end application as a link between a React.js Web application and the Agent on Dialogflow itself. By the end of the article, you should able to connect your own Dialogflow agent to your preferred web application.

    To make this guide easy to follow through, you can skip to whichever part of the tutorial interests you most or follow them in the following order as they appear:

    1. Setting Up A Dialogflow Agent

    As explained in this article, a chat assistant on Dialogflow is called an Agent and it comprises of smaller components such as intents, fulfillment, knowledge base and much more. Dialogflow provides a console for users to create, train, and design the conversation flow of an Agent. In our use case, we will restore an agent that was exported into a ZIP folder after being trained, using the agent Export and Import feature.

    Before we perform the import, we need to create a new agent that will be merged with the agent about to be restored. To create a new Agent from the console, a unique name is needed and also, a project on the Google Cloud to link the agent with. If there is no existing project on the Google Cloud to link with, a new one can be created here.

    An agent has been previously created and trained to recommend wine products to a user based on their budget. This agent has been exported into a ZIP; you can download the folder here and restore it into our newly created agent from the Export and Import tab found in the agent Settings page.

    Restoring a previously exported agent from a ZIP folder
    Restoring a previously exported agent from a ZIP folder. (Large preview)

    The imported agent has been previously trained to recommend a wine product to the user based on the user’s budget for purchasing a bottle of wine.

    Going through the imported agent, we will see it has three created intents from the intents page. One being a fallback intent, used when the Agent does not recognize input from a user, the other is a Welcome intent used when a conversation with the Agent is started, and the last intent is used to recommend a wine to the user based on the amount parameter within the sentence. Of concern to us is the get-wine-recommendation intent

    This intent has a single input context of wine-recommendation coming from the Default Welcome intent to link the conversation to this intent.

    “A Context is a system within an Agent used to control the flow of a conversation from one intent to the other.”

    Below the contexts are the Training phrases, which are sentences used to train an agent on what type of statements to expect from a user. Through a large variety of training phrases within an intent, an agent is able to recognize a user’s sentence and the intent it falls into.

    The training phrases within our agents get-wine-recommendation intent (as shown below) indicates the wine choice and the price category:

    List of available training phrases with get-wine-recommendation intent.
    Get-wine-recommendation intent page showing the available training phrases. (Large preview)

    Looking at the image above, we can see the available training phrases listed out, and the currency figure is highlighted in yellow color for each of them. This highlighting is known as an annotation on Dialogflow and it is automatically done to extract the recognized data types known as an entity from a user’s sentence.

    After this intent has been matched in a conversation with the agent, an HTTP request will be made to an external service to get the recommended wine based on the price extracted as a parameter from a user’s sentence, through the use of the enabled webhook found within the Fulfillment section at the bottom of this intent page.

    We can test the agent using the Dialogflow emulator located in the right section of the Dialogflow console. To test, we start the conversation with a “Hi” message and follow up with the desired amount of wine. The webhook will immediately be called and a rich response similar to the one below will be shown by the agent.

    Testing the imported agent agent webhook.
    Testing the imported agent’s fulfillment webhook using the Agent emulator in the console. (Large preview)

    From the image above we can see the webhook URL generated using Ngrok and the agent’s response on the right-hand side showing a wine within the $20 price range typed in by the user.

    At this point, the Dialogflow agent has been fully setup. We can now get started with integrating this agent into a web application to enable other users to access and interact with the agent without access to our Dialogflow console.

    Integrating A Dialogflow Agent

    While there are other means of connecting to a Dialogflow Agent such as making HTTP requests to its REST endpoints, the recommended way to connect to Dialogflow is through the use of its official client library available in several programming languages. For JavaScript, the @google-cloud/dialogflow package is available for installation from NPM.

    Internally the @google-cloud/dialogflow package uses gRPC for its network connections and this makes the package unsupported within a browser environment except when patched using webpack, the recommended way to use this package is from a Node environment. We can do this by setting up an Express.js back-end application to use this package then serve data to the web application through its API endpoints and this is what we will do next.

    Setting Up A Node Express Application

    To set up an express application we create a new project directory then grab the needed dependencies using yarn from an opened command line terminal.

    # create a new directory and ( && ) move into directory
    mkdir dialogflow-server && cd dialogflow-server
    
    # create a new Node project
    yarn init -y
    
    # Install needed packages
    yarn add express cors dotenv uuid
    

    With the needed dependencies installed, we can proceed to set up a very lean Express.js server that handles connections on a specified port with CORS support enabled for the web app.

    // index.js
    const express =  require("express")
    const dotenv =  require("dotenv")
    const cors =  require("cors")
    
    dotenv.config();
    
    const app = express();
    const PORT = process.env.PORT || 5000;
    
    app.use(cors());
    
    app.listen(PORT, () => console.log(`🔥  server running on port ${PORT}`));

    When executed, the code in the snippet above starts an HTTP server that listens for connections on a specified PORT Express.js. It also has Cross-origin resource sharing (CORS) enabled on all requests using the cors package as an Express middleware. For now, this server only listens for connections, it cannot respond to a request because it has no created route, so let’s create this.

    We now need to add two new routes: one for sending text data while the other for sending a recorded voice input. They will both accept a POST request and send the data contained in the request body to the Dialogflow agent later on.

    const express = require("express") 
    
    const app = express()
    
    app.post("/text-input", (req, res) => {
      res.status(200).send({ data : "TEXT ENDPOINT CONNECTION SUCCESSFUL" })
    });
    
    app.post("/voice-input", (req, res) => {
      res.status(200).send({ data : "VOICE ENDPOINT CONNECTION SUCCESSFUL" })
    });
    
    module.exports = app

    Above we created a separate router instance for the two created POST routes which for now, only respond with a 200 status code and a hardcoded dummy response. When we are done authenticating with Dialogflow, we can come back to implement an actual connection to Dialogflow within these endpoints.

    For the last step in our backend application setup, we mount the previously created router instance created into the Express application using app.use and a base path for the route.

    // agentRoutes.js
    
    const express =  require("express")
    const dotenv =  require("dotenv")
    const cors =  require("cors")
    
    const Routes =  require("./routes")
    
    dotenv.config();
    const app = express();
    const PORT = process.env.PORT || 5000;
    
    app.use(cors());
    
    app.use("/api/agent", Routes);
    
    app.listen(PORT, () => console.log(`🔥  server running on port ${PORT}`));
    

    Above, we have added a base path to the two routes two we can test any of them via a POST request using cURL from a command line as it’s done below with an empty request body;

    curl -X http://localhost:5000/api/agent/text-response

    After successful completion of the request above, we can expect to see a response containing object data being printed out to the console.

    Now we are left with making an actual connection with Dialogflow which includes handling authentication, sending, and receiving data from the Agent on Dialogflow using the @google-cloud/dialogflow package.

    Authenticating With Dialogflow

    Every Dialogflow agent created is linked to a project on the Google Cloud. To connect externally to the Dialogflow agent, we authenticate with the project on the Google cloud and use Dialogflow as one of the project’s resources. Out of the six available ways to connect to a project on the google-cloud, using the Service accounts option is the most convenient when connecting to a particular service on the google cloud through its client library.

    Note: For production-ready applications, the use of short-lived API keys are recommended over Service Account keys in order to reduce the risk of a service account key getting into the wrong hands.

    What Are Service Accounts?

    Service accounts are a special type of account on the Google Cloud, created for non-human interaction, mostly through external APIs. In our application, the service account will be accessed through a generated key by the Dialogflow client library to authenticate with the Google Cloud.

    The cloud documentation on creating and managing service accounts provides an excellent guide to create a service account. When creating the service account, the Dialogflow API Admin role should be assigned to the created service account as shown in the last step. This role gives the service account administrative control over the linked Dialogflow agent.

    To use the service account, we need to create a Service Account Key. The following steps below outline how to create one in JSON format:

    1. Click on the newly created Service Account to navigate to the Service account page.
    2. Scroll to the Keys section and click the Add Key dropdown and click on the Create new key option which opens a modal.
    3. Select a JSON file format and click the Create button at the bottom right of the modal.

    Note: It is recommended to keep a service account key private and not commit it to any version control system as it contains highly sensitive data about a project on the Google Cloud. This can be done by adding the file to the .gitignore file.

    With a service account created and a service account key available within our project’s directory, we can use the Dialogflow client library to send and receive data from the Dialogflow agent.

    // agentRoute.js
    require("dotenv").config();
    
    const express = require("express")
    const Dialogflow = require("@google-cloud/dialogflow")
    const { v4 as uuid } = require("uuid")
    const Path = require("path")
     
    const app = express();
    
    app.post("/text-input", async (req, res) => {
      const { message } = req.body;
    
      // Create a new session
       const sessionClient = new Dialogflow.SessionsClient({
        keyFilename: Path.join(__dirname, "./key.json"),
      });
    
      const sessionPath = sessionClient.projectAgentSessionPath(
        process.env.PROJECT_ID,
        uuid()
      );
    
      // The dialogflow request object
      const request = {
        session: sessionPath,
        queryInput: {
          text: {
            // The query to send to the dialogflow agent
            text: message,
          },
        },
      };
    
      // Sends data from the agent as a response
      try {
        const responses = await sessionClient.detectIntent(request);
        res.status(200).send({ data: responses });
      } catch (e) {
        console.log(e);
        res.status(422).send({ e });
      }
    });
    
    module.exports = app;
    

    The entire route above sends data to the Dialogflow agent and receives a response through the following steps.

    • First
      It authenticates with the Google cloud then it creates a session with Dialogflow using the projectID of Google cloud project linked to the Dialogflow agent and also a random ID to identify the session created. In our application, we are creating a UUID identifier on each session created using the JavaScript UUID package. This is very useful when logging or tracing all conversations handled by a Dialogflow agent.
    • Second
      We create a request object data following the specified format in the Dialogflow documentation. This request object contains the created session and the message data gotten from the request body which is to be passed to the Dialogflow agent.
    • Third
      Using the detectIntent method from the Dialogflow session, we send the request object asynchronously and await the Agent’s response using ES6 async / await syntax in a try-catch block should the detectIntent method return an exception, we can catch the error and return it, rather than crashing the entire application. A sample of the response object returned from the Agent is provided in the Dialogflow documentation and can be inspected to know how to extract the data from the object.

    We can make use of Postman to test the Dialogflow connection implemented above in the dialogflow-response route. Postman is a collaboration platform for API development with features to test APIs built in either development or production stages.

    Note: If not already installed, the Postman desktop application is not needed to test an API. Starting from September 2020, Postman’s web client moved into a Generally Available (GA) state and can be used directly from a browser.

    Using the Postman Web Client, we can either create a new workspace or use an existing one to create a POST request to our API endpoint at http://localhost:5000/api/agent/text-input and add data with a key of message and value of “Hi There” into the query parameters.

    At the click of the Send button, a POST request will be made to the running Express server — with a response similar to the one shown in the image below:

    Testing the text-input API endpoint using Postman.
    Testing the text-input API endpoint using Postman. (Large preview)

    Within the image above, we can see the prettified response data from the Dialogflow agent through the Express server. The data returned is formatted according to the sample response definition given in the Dialogflow Webhook documentation.

    Handling Voice Inputs

    By default, all Dialogflow agents are enabled to process both text and audio data and also return a response in either text or audio format. However, working with audio input or output data can be a bit more complex than text data.

    To handle and process voice inputs, we will begin the implementation for the /voice-input endpoint that we have previously created in order to receive audio files and send them to Dialogflow in exchange for a response from the Agent:

    // agentRoutes.js
    import { pipeline, Transform } from "stream";
    import busboy from "connect-busboy";
    import util from "promisfy"
    import Dialogflow from "@google-cloud/dialogflow"
    
    const app = express();
    
    app.use(
      busboy({
        immediate: true,
      })
    );
    
    app.post("/voice-input", (req, res) => {
      const sessionClient = new Dialogflow.SessionsClient({
        keyFilename: Path.join(__dirname, "./recommender-key.json"),
      });
      const sessionPath = sessionClient.projectAgentSessionPath(
        process.env.PROJECT_ID,
        uuid()
      );
    
      // transform into a promise
      const pump = util.promisify(pipeline);
    
      const audioRequest = {
        session: sessionPath,
        queryInput: {
          audioConfig: {
            audioEncoding: "AUDIO_ENCODING_OGG_OPUS",
            sampleRateHertz: "16000",
            languageCode: "en-US",
          },
          singleUtterance: true,
        },
      };
      
      const streamData = null;
      const detectStream = sessionClient
        .streamingDetectIntent()
        .on("error", (error) => console.log(error))
        .on("data", (data) => {
          streamData = data.queryResult    
        })
        .on("end", (data) => {
          res.status(200).send({ data : streamData.fulfillmentText }}
        }) 
      
      detectStream.write(audioRequest);
    
      try {
        req.busboy.on("file", (_, file, filename) => {
          pump(
            file,
            new Transform({
              objectMode: true,
              transform: (obj, _, next) => {
                next(null, { inputAudio: obj });
              },
            }),
            detectStream
          );
        });
      } catch (e) {
        console.log(`error  : ${e}`);
      }
    });
    

    At a high overview, the /voice-input route above receives a user’s voice input as a file containing the message being spoken to the chat assistant and sends it to the Dialogflow agent. To understand this process better, we can break it down into the following smaller steps:

    • First, we add and use connect-busboy as an Express middleware for parsing form data being sent in the request from the web application. After which we authenticate with Dialogflow using the Service Key and create a session, the same way we did in the previous route.
      Then using the promisify method from the built-in Node.js util module, we get and save a promise equivalent of the Stream pipeline method to be used later to pipe multiple streams and also perform a clean up after the streams are completed.
    • Next, We create a request object containing the Dialogflow authentication session and a configuration for the audio file about to be sent to Dialogflow. The nested audio configuration object enables the Dialogflow agent to perform a Speech-To-Text conversion on the sent audio file.
    • Next, using the created session and the request object, we detect a user’s intent from the audio file using detectStreamingIntent method which opens up a new data stream from the Dialogflow agent to the backend application. Data will send back in small bits through this stream and using the data “event” from the readable stream we store the data in streamData variable for later use. After the stream is closed the “end” event is fired and we send the response from the Dialogflow agent stored in the streamData variable to the Web Application.
    • Lastly using the file stream event from connect-busboy, we receive the stream of the audio file sent in the request body and we further pass it into the promise equivalent of Pipeline which we created previously. The function of this is to pipe the audio file stream coming in from request to the Dialogflow stream, we pipe the audio file stream to the stream opened by the detectStreamingIntent method above.

    To test and confirm that the steps above are working as laid out, we can make a test request containing an audio file in the request body to the /voice-input endpoint using Postman.

    Testing the voice-input API endpoint using Postman.
    Testing the voice-input API endpoint using postman with a recorded voice file. (Large preview)

    The Postman result above shows the response gotten after making a POST request with the form-data of a recorded voice note message saying “Hi” included in the request’s body.

    At this point, we now have a functional Express.js application that sends and receives data from Dialogflow, the two parts of this article are done. Where are now left with integrating this Agent into a web application by consuming the APIs created here from a Reactjs application.

    Integrating Into A Web Application

    To consume our built REST API we will expand this existing React.js application which already has a home page showing a list of wines fetched from an API and support for decorators using the babel proposal decorators plugin. We will refactor it a little by introducing Mobx for state management and also a new feature to recommend a wine from a chat component using the added REST API endpoints from the Express.js application.

    To get started, we begin to manage the application’s state using MobX as we create a Mobx store with a few observable values and some methods as actions.

    // store.js
    
    import Axios from "axios";
    import { action, observable, makeObservable, configure } from "mobx";
    
    const ENDPOINT = process.env.REACT_APP_DATA_API_URL;
    
    class ApplicationStore {
      constructor() {
        makeObservable(this);
      }
    
      @observable
      isChatWindowOpen = false;
    
      @observable
      isLoadingChatMessages = false;
    
      @observable
      agentMessages = [];
    
      @action
      setChatWindow = (state) => {
        this.isChatWindowOpen = state;
      };
    
      @action
      handleConversation = (message) => {
         this.isLoadingChatMessages = true;
         this.agentMessages.push({ userMessage: message });
    
         Axios.post(`${ENDPOINT}/dialogflow-response`, {
          message: message || "Hi",
         })
          .then((res) => {
            this.agentMessages.push(res.data.data[0].queryResult);
            this.isLoadingChatMessages = false;
          })
          .catch((e) => {
            this.isLoadingChatMessages = false;
            console.log(e);
          });
      };
    }
    
    export const store = new ApplicationStore();

    Above we created a store for the chat component feature within the application having the following values:

    • isChatWindowOpen
      The value stored here controls the visibility of the chat component where the messages of Dialogflow are shown.
    • isLoadingChatMessages
      This is used to show a loading indicator when a request to fetch a response from the Dialogflow agent is made.
    • agentMessages
      This array stores all responses coming from the requests made to get a response from the Dialogflow agent. The data in the array is later displayed in the component.
    • handleConversation
      This method decorated as an action adds data into the agentMessages array. First, it adds the user’s message passed in as an argument then makes a request using Axios to the backend application to get a response from Dialogflow. After the request is resolved, it adds the response from the request into the agentMessages array.

    Note: In the absence of the decorators support in an Application, MobX provides makeObservable which can be used in the constructor of the target store class. See an example here.

    With the store setup, we need to wrap the entire application tree with the MobX Provider higher-order component starting from the root component in the index.js file.

    import React from "react";
    import { Provider } from "mobx-react";
    
    import { store } from "./state/";
    import Home from "./pages/home";
    
    function App() {
      return (
        <Provider ApplicationStore={store}>
          <div className="App">
            <Home />
          </div>
        </Provider>
      );
    }
    
    export default App;
    

    Above we wrap the root App component with MobX Provider and we pass in the previously created store as one of the Provider’s values. Now we can proceed to read from the store within components connected to the store.

    Creating A Chat Interface

    To display the messages sent or received from the API requests, we need a new component with some chat interface showing the messages listed out. To do this, we create a new component to display some hard-coded messages first then later we display messages in an ordered list.

    // ./chatComponent.js
    
    import React, { useState } from "react";
    import { FiSend, FiX } from "react-icons/fi";
    import "../styles/chat-window.css";
    
    const center = {
      display: "flex",
      jusitfyContent: "center",
      alignItems: "center",
    };
    
    const ChatComponent = (props) => {
      const { closeChatwindow, isOpen } = props;
      const [Message, setMessage] = useState("");
    
      return (
       <div className="chat-container">
          <div className="chat-head">
            <div style={{ ...center }}>
              <h5> Zara </h5>
            </div>
            <div style={{ ...center }} className="hover">
              <FiX onClick={() => closeChatwindow()} />
            </div>
          </div>
          <div className="chat-body">
            <ul className="chat-window">
              <li>
                <div className="chat-card">
                  <p>Hi there, welcome to our Agent</p>
                </div>
              </li>
            </ul>
            <hr style={{ background: "#fff" }} />
            <form onSubmit={(e) => {}} className="input-container">
              <input
                className="input"
                type="text"
                onChange={(e) => setMessage(e.target.value)}
                value={Message}
                placeholder="Begin a conversation with our agent"
              />
              <div className="send-btn-ctn">
                <div className="hover" onClick={() => {}}>
                  <FiSend style={{ transform: "rotate(50deg)" }} />
                </div>
              </div>
            </form>
          </div>
        </div>
      );
    };
    
    export default ChatComponent

    The component above has the basic HTML markup needed for a chat application. It has a header showing the Agent’s name and an icon for closing the chat window, a message bubble containing a hardcoded text in a list tag, and lastly an input field having an onChange event handler attached to the input field to store the text typed into the component’s local state using React’s useState.

    A preview of the Chat Component with a hard coded message from the chat Agent
    A preview of the Chat Component with a hard coded message from the chat Agent. (Large preview)

    From the image above, the chat component works as it should, showing a styled chat window having a single chat message and the input field at the bottom. We however want the message shown to be actual responses gotten from the API request and not hardcoded text.

    We move forward to refactor the Chat component, this time connecting and making use of values in the MobX store within the component.

    // ./components/chatComponent.js
    
    import React, { useState, useEffect } from "react";
    import { FiSend, FiX } from "react-icons/fi";
    import { observer, inject } from "mobx-react";
    import { toJS } from "mobx";
    import "../styles/chat-window.css";
    
    const center = {
      display: "flex",
      jusitfyContent: "center",
      alignItems: "center",
    };
    
    const ChatComponent = (props) => {
      const { closeChatwindow, isOpen } = props;
      const [Message, setMessage] = useState("");
    
      const {
        handleConversation,
        agentMessages,
        isLoadingChatMessages,
      } = props.ApplicationStore;
    
      useEffect(() => {
        handleConversation();
        return () => handleConversation()
      }, []);
    
      const data = toJS(agentMessages);
     
      return (
            <div className="chat-container">
              <div className="chat-head">
                <div style={{ ...center }}>
                  <h5> Zara {isLoadingChatMessages && "is typing ..."} </h5>
                </div>
                <div style={{ ...center }} className="hover">
                  <FiX onClick={(_) => closeChatwindow()} />
                </div>
              </div>
              <div className="chat-body">
                <ul className="chat-window">
                  {data.map(({ fulfillmentText, userMessage }) => (
                    <li>
                      {userMessage && (
                        <div
                          style={{
                            display: "flex",
                            justifyContent: "space-between",
                          }}
                        >
                          <p style={{ opacity: 0 }}> . </p>
                          <div
                            key={userMessage}
                            style={{
                              background: "red",
                              color: "white",
                            }}
                            className="chat-card"
                          >
                            <p>{userMessage}</p>
                          </div>
                        </div>
                      )}
                      {fulfillmentText && (
                        <div
                          style={{
                            display: "flex",
                            justifyContent: "space-between",
                          }}
                        >
                          <div key={fulfillmentText} className="chat-card">
                            <p>{fulfillmentText}</p>
                          </div>
                          <p style={{ opacity: 0 }}> . </p>
                        </div>
                      )}
                    </li>
                  ))}
                </ul>
                <hr style={{ background: "#fff" }} />
                <form
                  onSubmit={(e) => {
                    e.preventDefault();
                    handleConversation(Message);
                  }}
                  className="input-container"
                >
                  <input
                    className="input"
                    type="text"
                    onChange={(e) => setMessage(e.target.value)}
                    value={Message}
                    placeholder="Begin a conversation with our agent"
                  />
                  <div className="send-btn-ctn">
                    <div
                      className="hover"
                      onClick={() => handleConversation(Message)}
                    >
                      <FiSend style={{ transform: "rotate(50deg)" }} />
                    </div>
                  </div>
                </form>
              </div>
            </div>
         );
    };
    
    export default inject("ApplicationStore")(observer(ChatComponent));
    

    From the highlighted parts of the code above, we can see that the entire chat component has now been modified to perform the following new operations;

    • It has access to the MobX store values after injecting the ApplicationStore value. The component has also been made an observer of these store values so it re-renders when one of the values changes.
    • We start the conversation with the Agent immediately after the chat component is opened by invoking the handleConversation method within a useEffect hook to make a request immediately the component is rendered.
    • We are now making use of the isLoadingMessages value within the Chat component header. When a request to get a response from the Agent is in flight, we set the isLoadingMessages value to true and update the header to Zara is typing…
    • The agentMessages array within the store gets updated to a proxy by MobX after its values are set. From this component, we convert that proxy back to an array using the toJS utility from MobX and store the values in a variable within the component. That array is further iterated upon to populate the chat bubbles with the values within the array using a map function.

    Now using the chat component we can type in a sentence and wait for a response to be displayed in the agent.

    Chat component showing a list data returned from the HTTP request to the express application.
    Chat component showing a list data returned from the HTTP request to the express application. (Large preview)

    Recording User Voice Input

    By default, all Dialogflow agents can accept either voice or text-based input in any specified language from a user. However, it requires a few adjustments from a web application to gain access to a user’s microphone and record a voice input.

    To achieve this, we modify the MobX store to use the HTML MediaStream Recording API to record a user’s voice within two new methods in the MobX store.

    // store.js
    
    import Axios from "axios";
    import { action, observable, makeObservable } from "mobx";
    
    class ApplicationStore {
      constructor() {
        makeObservable(this);
      }
    
      @observable
      isRecording = false;
    
      recorder = null;
      recordedBits = [];
    
      @action
      startAudioConversation = () => {
        navigator.mediaDevices
          .getUserMedia({
            audio: true,
          })
          .then((stream) => {
            this.isRecording = true;
            this.recorder = new MediaRecorder(stream);
            this.recorder.start(50);
    
            this.recorder.ondataavailable = (e) => {
               this.recordedBits.push(e.data);
            };
          })
          .catch((e) => console.log(`error recording : ${e}`));
      };
    };
    

    At the click of the record icon from the chat component, the startAudioConversation method in the MobX store above is invoked to set the method the observable isRecording property is to true , for the chat component to provide visual feedback to show a recording is in progress.

    Using the browser’s navigator interface, the Media Device object is accessed to request the user’s device microphone. After permission is granted to the getUserMedia request, it resolves its promise with a MediaStream data which we further pass to the MediaRecorder constructor to create a recorder using the media tracks in the stream returned from the user’s device microphone. We then store the Media recorder instance in the store’s recorder property as we will access it from another method later on.

    Next, we call the start method on the recorder instance, and after the recording session is ended, the ondataavailable function is fired with an event argument containing the recorded stream in a Blob which we store in the recordedBits array property.

    Logging out the data in the event argument passed into the fired ondataavailable event, we can see the Blob and its properties in the browser console.

    Browser Devtools console showing logged out Blob created by the Media Recorder after a recording is ended.Pull Quotes
    Browser Devtools console showing logged out Blob created by the Media Recorder after a recording is ended. (Large preview)

    Now that we can start a MediaRecorder stream, we need to be able to stop the MediaRecorder stream when a user is done recording their voice input and send the generated audio file to the Express.js application.

    The new method added to the store below stops the stream and makes a POST request containing the recorded voice input.

    //store.js
    
    import Axios from "axios";
    import { action, observable, makeObservable, configure } from "mobx";
    
    const ENDPOINT = process.env.REACT_APP_DATA_API_URL;
    
    class ApplicationStore {
      constructor() {
        makeObservable(this);
      }
    
      @observable
      isRecording = false;
    
      recorder = null;
      recordedBits = []; 
    
      @action
      closeStream = () => {
        this.isRecording = false;
        this.recorder.stop();
        
        this.recorder.onstop = () => {
          if (this.recorder.state === "inactive") {
            const recordBlob = new Blob(this.recordedBits, {
              type: "audio/mp3",
            });
    
            const inputFile = new File([recordBlob], "input.mp3", {
              type: "audio/mp3",
            });
            const formData = new FormData();
            formData.append("voiceInput", inputFile);
    
            Axios.post(`${ENDPOINT}/api/agent/voice-input`, formData, {
              headers: {
                "Content-Type": "multipart/formdata",
              },
            })
              .then((data) => {})
              .catch((e) => console.log(`error uploading audio file : ${e}`));
          }
        };
      };
    }
    
    export const store = new ApplicationStore();

    The method above executes the MediaRecorder’s stop method to stop an active stream. Within the onstop event fired after the MediaRecorder is stopped, we create a new Blob with a music type and append it into a created FormData.

    As the last step., we make POST request with the created Blob added to the request body and a Content-Type: multipart/formdata added to the request’s headers so the file can be parsed by the connect-busboy middleware from the backend-service application.

    With the recording being performed from the MobX store, all we need to add to the chat-component is a button to execute the MobX actions to start and stop the recording of the user’s voice and also a text to show when a recording session is active.

    import React from 'react'
    
    const ChatComponent = ({ ApplicationStore }) => {
      const {
         startAudiConversation,
         isRecording,
         handleConversation,
         endAudioConversation,
         isLoadingChatMessages
        } = ApplicationStore
    
      const [ Message, setMessage ] = useState("") 
    
        return (
            <div>
               <div className="chat-head">
                <div style={{ ...center }}>
                  <h5> Zara {} {isRecording && "is listening ..."} </h5>
                </div>
                <div style={{ ...center }} className="hover">
                  <FiX onClick={(_) => closeChatwindow()} />
                </div>
              </div>          
       
              <form
                  onSubmit={(e) => {
                      e.preventDefault();
                      handleConversation(Message);
                    }}
                    className="input-container"
                  >
                    <input
                      className="input"
                      type="text"
                      onChange={(e) => setMessage(e.target.value)}
                      value={Message}
                      placeholder="Begin a conversation with our agent"
                    />
                    <div className="send-btn-ctn">
                      {Message.length > 0 ? (
                        <div
                          className="hover"
                          onClick={() => handleConversation(Message)}
                        >
                          <FiSend style={{ transform: "rotate(50deg)" }} />
                        </div>
                      ) : (
                        <div
                          className="hover"
                          onClick={() =>  handleAudioInput()}
                        >
                          <FiMic />
                        </div>
                      )}
                    </div>
                  </form>
            </div>     
        )
    }
    
    export default ChatComponent

    From the highlighted part in the chat component header above, we use the ES6 ternary operators to switch the text to “Zara is listening ….” whenever a voice input is being recorded and sent to the backend application. This gives the user feedback on what is being done.

    Also, besides the text input, we added a microphone icon to inform the user of the text and voice input options available when using the chat assistant. If a user decides to use the text input, we switch the microphone button to a Send button by counting the length of the text stored and using a ternary operator to make the switch.

    We can test the newly connected chat assistant a couple of times by using both voice and text inputs and watch it respond exactly like it would when using the Dialogflow console!

    Conclusion

    In the coming years, the use of language processing chat assistants in public services will have become mainstream. This article has provided a basic guide on how one of these chat assistants built with Dialogflow can be integrated into your own web application through the use of a backend application.

    The built application has been deployed using Netlify and can be found here. Feel free to explore the Github repository of the backend express application here and the React.js web application here. They both contain a detailed README to guide you on the files within the two projects.

    References

    Smashing Editorial
    (ks, vf, yk, il)

    Source link

    web design

    Building A Stocks Price Notifier App Using React, Apollo GraphQL And Hasura — Smashing Magazine

    12/21/2020

    About The Author

    Software Engineer, trying to make sense of every line of code she writes. Ankita is a JavaScript Enthusiast and adores its weird parts. She’s also an obsessed …
    More about
    Ankita
    Masand

    In this article, we’ll learn how to build an event-based application and send a web-push notification when a particular event is triggered. We’ll set up database tables, events, and scheduled triggers on the Hasura GraphQL engine and wire up the GraphQL endpoint to the front-end application to record the stock price preference of the user.

    The concept of getting notified when the event of your choice has occurred has become popular compared to being glued onto the continuous stream of data to find that particular occurrence yourself. People prefer to get relevant emails/messages when their preferred event has occurred as opposed to being hooked on the screen to wait for that event to happen. The events-based terminology is also quite common in the world of software.

    How awesome would that be if you could get the updates of the price of your favorite stock on your phone?

    In this article, we’re going to build a Stocks Price Notifier application by using React, Apollo GraphQL, and Hasura GraphQL engine. We’re going to start the project from a create-react-app boilerplate code and would build everything ground up. We’ll learn how to set up the database tables, and events on the Hasura console. We’ll also learn how to wire up Hasura’s events to get stock price updates using web-push notifications.

    Here’s a quick glance at what we would be building:

    Overview of Stock Price Notifier Application
    Stock Price Notifier Application

    Let’s get going!

    An Overview Of What This Project Is About

    The stocks data (including metrics such as high, low, open, close, volume) would be stored in a Hasura-backed Postgres database. The user would be able to subscribe to a particular stock based on some value or he can opt to get notified every hour. The user will get a web-push notification once his subscription criteria are fulfilled.

    This looks like a lot of stuff and there would obviously be some open questions on how we’ll be building out these pieces.

    Here’s a plan on how we would accomplish this project in four steps:

    1. Fetching the stocks data using a NodeJs script
      We’ll start by fetching the stock data using a simple NodeJs script from one of the providers of stocks API — Alpha Vantage. This script will fetch the data for a particular stock in intervals of 5mins. The response of the API includes high, low, open, close and volume. This data will be then be inserted in the Postgres database that is integrated with the Hasura back-end.
    2. Setting up The Hasura GraphQL engine
      We’ll then set-up some tables on the Postgres database to record data points. Hasura automatically generates the GraphQL schemas, queries, and mutations for these tables.
    3. Front-end using React and Apollo Client
      The next step is to integrate the GraphQL layer using the Apollo client and Apollo Provider (the GraphQL endpoint provided by Hasura). The data-points will be shown as charts on the front-end. We’ll also build the subscription options and will fire corresponding mutations on the GraphQL layer.
    4. Setting up Event/Scheduled triggers
      Hasura provides an excellent tooling around triggers. We’ll be adding event & scheduled triggers on the stocks data table. These triggers will be set if the user is interested in getting a notification when the stock prices reach a particular value (event trigger). The user can also opt for getting a notification of a particular stock every hour (scheduled trigger).

    Now that the plan is ready, let’s put it into action!

    Here’s the GitHub repository for this project. If you get lost anywhere in the code below, refer to this repository and get back to speed!

    Fetching The Stocks Data Using A NodeJs Script

    This is not that complicated as it sounds! We’ll have to write a function that fetches data using the Alpha Vantage endpoint and this fetch call should be fired in an interval of 5 mins (You guessed it right, we’ll have to put this function call in setInterval).

    If you’re still wondering what Alpha Vantage is and just want to get this out of your head before hopping onto the coding part, then here it is:

    Alpha Vantage Inc. is a leading provider of free APIs for realtime and historical data on stocks, forex (FX), and digital/cryptocurrencies.

    We would be using this endpoint to get the required metrics of a particular stock. This API expects an API key as one of the parameters. You can get your free API key from here. We’re now good to get onto the interesting bit — let’s start writing some code!

    Installing Dependencies

    Create a stocks-app directory and create a server directory inside it. Initialize it as a node project using npm init and then install these dependencies:

    npm i isomorphic-fetch pg nodemon --save

    These are the only three dependencies that we’d need to write this script of fetching the stock prices and storing them in the Postgres database.

    Here’s a brief explanation of these dependencies:

    • isomorphic-fetch
      It makes it easy to use fetch isomorphically (in the same form) on both the client and the server.
    • pg
      It is a non-blocking PostgreSQL client for NodeJs.
    • nodemon
      It automatically restarts the server on any file changes in the directory.
    Setting up the configuration

    Add a config.js file at the root level. Add the below snippet of code in that file for now:

    const config = {
      user: '<DATABASE_USER>',
      password: '<DATABASE_PASSWORD>',
      host: '<DATABASE_HOST>',
      port: '<DATABASE_PORT>',
      database: '<DATABASE_NAME>',
      ssl: '<IS_SSL>',
      apiHost: 'https://www.alphavantage.co/',
    };
    
    module.exports = config;

    The user, password, host, port, database, ssl are related to the Postgres configuration. We’ll come back to edit this while we set up the Hasura engine part!

    Initializing The Postgres Connection Pool For Querying The Database

    A connection pool is a common term in computer science and you’ll often hear this term while dealing with databases.

    While querying data in databases, you’ll have to first establish a connection to the database. This connection takes in the database credentials and gives you a hook to query any of the tables in the database.

    Note: Establishing database connections is costly and also wastes significant resources. A connection pool caches the database connections and re-uses them on succeeding queries. If all the open connections are in use, then a new connection is established and is then added to the pool.

    Now that it is clear what the connection pool is and what is it used for, let’s start by creating an instance of the pg connection pool for this application:

    Add pool.js file at the root level and create a pool instance as:

    const { Pool } = require('pg');
    const config = require('./config');
    
    const pool = new Pool({
      user: config.user,
      password: config.password,
      host: config.host,
      port: config.port,
      database: config.database,
      ssl: config.ssl,
    });
    
    module.exports = pool;

    The above lines of code create an instance of Pool with the configuration options as set in the config file. We’re yet to complete the config file but there won’t be any changes related to the configuration options.

    We’ve now set the ground and are ready to start making some API calls to the Alpha Vantage endpoint.

    Let’s get onto the interesting bit!

    Fetching The Stocks Data

    In this section, we’ll be fetching the stock data from the Alpha Vantage endpoint. Here’s the index.js file:

    const fetch = require('isomorphic-fetch');
    const getConfig = require('./config');
    const { insertStocksData } = require('./queries');
    
    const symbols = [
      'NFLX',
      'MSFT',
      'AMZN',
      'W',
      'FB'
    ];
    
    (function getStocksData () {
    
      const apiConfig = getConfig('apiHostOptions');
      const { host, timeSeriesFunction, interval, key } = apiConfig;
    
      symbols.forEach((symbol) => {
        fetch(`${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key}`)
        .then((res) => res.json())
        .then((data) => {
          const timeSeries = data['Time Series (5min)'];
          Object.keys(timeSeries).map((key) => {
            const dataPoint = timeSeries[key];
            const payload = [
              symbol,
              dataPoint['2. high'],
              dataPoint['3. low'],
              dataPoint['1. open'],
              dataPoint['4. close'],
              dataPoint['5. volume'],
              key,
            ];
            insertStocksData(payload);
          });
        });
      })
    })()

    For the purpose of this project, we’re going to query prices only for these stocks — NFLX (Netflix), MSFT (Microsoft), AMZN (Amazon), W (Wayfair), FB (Facebook).

    Refer this file for the config options. The IIFE getStocksData function is not doing much! It loops through these symbols and queries the Alpha Vantage endpoint ${host}query/?function=${timeSeriesFunction}&symbol=${symbol}&interval=${interval}&apikey=${key} to get the metrics for these stocks.

    The insertStocksData function puts these data points in the Postgres database. Here’s the insertStocksData function:

    const insertStocksData = async (payload) => {
      const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';
      pool.query(query, payload, (err, result) => {
        console.log('result here', err);
      });
    };

    This is it! We have fetched data points of the stock from the Alpha Vantage API and have written a function to put these in the Postgres database in the stock_data table. There is just one missing piece to make all this work! We’ve to populate the correct values in the config file. We’ll get these values after setting up the Hasura engine. Let’s get to that right away!

    Please refer to the server directory for the complete code on fetching data points from Alpha Vantage endpoint and populating that to the Hasura Postgres database.

    If this approach of setting up connections, configuration options, and inserting data using the raw query looks a bit difficult, please don’t worry about that! We’re going to learn how to do all this the easy way with a GraphQL mutation once the Hasura engine is set up!

    Setting Up The Hasura GraphQL Engine

    It is really simple to set up the Hasura engine and get up and running with the GraphQL schemas, queries, mutations, subscriptions, event triggers, and much more!

    Click on Try Hasura and enter the project name:

    Creating a Hasura Project
    Creating a Hasura Project. (Large preview)

    I’m using the Postgres database hosted on Heroku. Create a database on Heroku and link it to this project. You should then be all set to experience the power of query-rich Hasura console.

    Please copy the Postgres DB URL that you’ll get after creating the project. We’ll have to put this in the config file.

    Click on Launch Console and you’ll be redirected to this view:

    Hasura Console
    Hasura Console. (Large preview)

    Let’s start building the table schema that we’d need for this project.

    Creating Tables Schema On The Postgres Database

    Please go to the Data tab and click on Add Table! Let’s start creating some of the tables:

    symbol table

    This table would be used for storing the information of the symbols. For now, I’ve kept two fields here — id and company. The field id is a primary key and company is of type varchar. Let’s add some of the symbols in this table:

    symbol table
    symbol table. (Large preview)
    stock_data table

    The stock_data table stores id, symbol, time and the metrics such as high, low, open, close, volume. The NodeJs script that we wrote earlier in this section will be used to populate this particular table.

    Here’s how the table looks like:

    stock_data table
    stock_data table. (Large preview)

    Neat! Let’s get to the other table in the database schema!

    user_subscription table

    The user_subscription table stores the subscription object against the user Id. This subscription object is used for sending web-push notifications to the users. We’ll learn later in the article how to generate this subscription object.

    There are two fields in this table — id is the primary key of type uuid and subscription field is of type jsonb.

    events table

    This is the important one and is used for storing the notification event options. When a user opts-in for the price updates of a particular stock, we store that event information in this table. This table contains these columns:

    • id: is a primary key with the auto-increment property.
    • symbol: is a text field.
    • user_id: is of type uuid.
    • trigger_type: is used for storing the event trigger type — time/event.
    • trigger_value: is used for storing the trigger value. For example, if a user has opted in for price-based event trigger — he wants updates if the price of the stock has reached 1000, then the trigger_value would be 1000 and the trigger_type would be event.

    These are all the tables that we’d need for this project. We also have to set up relations among these tables to have a smooth data flow and connections. Let’s do that!

    Setting up relations among tables

    The events table is used for sending web-push notifications based on the event value. So, it makes sense to connect this table with the user_subscription table to be able to send push notifications on the subscriptions stored in this table.

    events.user_id  → user_subscription.id

    The stock_data table is related to the symbols table as:

    stock_data.symbol  → symbol.id

    We also have to construct some relations on the symbol table as:

    stock_data.symbol  → symbol.id
    events.symbol  → symbol.id

    We’ve now created the required tables and also established the relations among them! Let’s switch to the GRAPHIQL tab on the console to see the magic!

    Hasura has already set up the GraphQL queries based on these tables:

    GraphQL Queries/Mutations on the Hasura console
    GraphQL Queries/Mutations on the Hasura console. (Large preview)

    It is plainly simple to query on these tables and you can also apply any of these filters/properties (distinct_on, limit, offset, order_by, where) to get the desired data.

    This all looks good but we have still not connected our server-side code to the Hasura console. Let’s complete that bit!

    Connecting The NodeJs Script To The Postgres Database

    Please put the required options in the config.js file in the server directory as:

    const config = {
      databaseOptions: {
        user: '<DATABASE_USER>',
        password: '<DATABASE_PASSWORD>',
        host: '<DATABASE_HOST>',
        port: '<DATABASE_PORT>',
        database: '<DATABASE_NAME>',
        ssl: true,
      },
      apiHostOptions: {
        host: 'https://www.alphavantage.co/',
        key: '<API_KEY>',
        timeSeriesFunction: 'TIME_SERIES_INTRADAY',
        interval: '5min'
      },
      graphqlURL: '<GRAPHQL_URL>'
    };
    
    const getConfig = (key) => {
      return config[key];
    };
    
    module.exports = getConfig;

    Please put these options from the database string that was generated when we created the Postgres database on Heroku.

    The apiHostOptions consists of the API related options such as host, key, timeSeriesFunction and interval.

    You’ll get the graphqlURL field in the GRAPHIQL tab on the Hasura console.

    The getConfig function is used for returning the requested value from the config object. We’ve already used this in index.js in the server directory.

    It’s time to run the server and populate some data in the database. I’ve added one script in package.json as:

    "scripts": {
        "start": "nodemon index.js"
    }

    Run npm start on the terminal and the data points of the symbols array in index.js should be populated in the tables.

    Refactoring The Raw Query In The NodeJs Script To GraphQL Mutation

    Now that the Hasura engine is set up, let’s see how easy can it be to call a mutation on the stock_data table.

    The function insertStocksData in queries.js uses a raw query:

    const query = 'INSERT INTO stock_data (symbol, high, low, open, close, volume, time) VALUES ($1, $2, $3, $4, $5, $6, $7)';

    Let’s refactor this query and use mutation powered by the Hasura engine. Here’s the refactored queries.js in the server directory:

    
    const { createApolloFetch } = require('apollo-fetch');
    const getConfig = require('./config');
    
    const GRAPHQL_URL = getConfig('graphqlURL');
    const fetch = createApolloFetch({
      uri: GRAPHQL_URL,
    });
    
    const insertStocksData = async (payload) => {
      const insertStockMutation = await fetch({
        query: `mutation insertStockData($objects: [stock_data_insert_input!]!) {
          insert_stock_data (objects: $objects) {
            returning {
              id
            }
          }
        }`,
        variables: {
          objects: payload,
        },
      });
      console.log('insertStockMutation', insertStockMutation);
    };
    
    module.exports = {
      insertStocksData
    }

    Please note: We’ve to add graphqlURL in the config.js file.

    The apollo-fetch module returns a fetch function that can be used to query/mutate the date on the GraphQL endpoint. Easy enough, right?

    The only change that we’ve to do in index.js is to return the stocks object in the format as required by the insertStocksData function. Please check out index2.js and queries2.js for the complete code with this approach.

    Now that we’ve accomplished the data-side of the project, let’s move onto the front-end bit and build some interesting components!

    Note: We don’t have to keep the database configuration options with this approach!

    Front-end Using React And Apollo Client

    The front-end project is in the same repository and is created using the create-react-app package. The service worker generated using this package supports assets caching but it doesn’t allow more customizations to be added to the service worker file. There are already some open issues to add support for custom service worker options. There are ways to get away with this problem and add support for a custom service worker.

    Let’s start by looking at the structure for the front-end project:

    Project Directory
    Project Directory. (Large preview)

    Please check the src directory! Don’t worry about the service worker related files for now. We’ll learn more about these files later in this section. The rest of the project structure looks simple. The components folder will have the components (Loader, Chart); the services folder contains some of the helper functions/services used for transforming objects in the required structure; styles as the name suggests contains the sass files used for styling the project; views is the main directory and it contains the view layer components.

    We’d need just two view components for this project — The Symbol List and the Symbol Timeseries. We’ll build the time-series using the Chart component from the highcharts library. Let’s start adding code in these files to build up the pieces on the front-end!

    Installing Dependencies

    Here’s the list of dependencies that we’ll need:

    • apollo-boost
      Apollo boost is a zero-config way to start using Apollo Client. It comes bundled with the default configuration options.
    • reactstrap and bootstrap
      The components are built using these two packages.
    • graphql and graphql-type-json
      graphql is a required dependency for using apollo-boost and graphql-type-json is used for supporting the json datatype being used in the GraphQL schema.
    • highcharts and highcharts-react-official
      And these two packages will be used for building the chart:

    • node-sass
      This is added for supporting sass files for styling.

    • uuid
      This package is used for generating strong random values.

    All of these dependencies will make sense once we start using them in the project. Let’s get onto the next bit!

    Setting Up Apollo Client

    Create a apolloClient.js inside the src folder as:

    import ApolloClient from 'apollo-boost';
    
    const apolloClient = new ApolloClient({
      uri: '<HASURA_CONSOLE_URL>'
    });
    
    export default apolloClient;

    The above code instantiates ApolloClient and it takes in uri in the config options. The uri is the URL of your Hasura console. You’ll get this uri field on the GRAPHIQL tab in the GraphQL Endpoint section.

    The above code looks simple but it takes care of the main part of the project! It connects the GraphQL schema built on Hasura with the current project.

    We also have to pass this apollo client object to ApolloProvider and wrap the root component inside ApolloProvider. This will enable all the nested components inside the main component to use client prop and fire queries on this client object.

    Let’s modify the index.js file as:

    const Wrapper = () => {
    /* some service worker logic - ignore for now */
      const [insertSubscription] = useMutation(subscriptionMutation);
      useEffect(() => {
        serviceWorker.register(insertSubscription);
      }, [])
      /* ignore the above snippet */
      return <App />;
    }
    
    ReactDOM.render(
      <ApolloProvider client={apolloClient}>
        <Wrapper />
      </ApolloProvider>,
      document.getElementById('root')
    );

    Please ignore the insertSubscription related code. We’ll understand that in detail later. The rest of the code should be simple to get around. The render function takes in the root component and the elementId as parameters. Notice client (ApolloClient instance) is being passed as a prop to ApolloProvider. You can check the complete index.js file here.

    Setting Up The Custom Service Worker

    A Service worker is a JavaScript file that has the capability to intercept network requests. It is used for querying the cache to check if the requested asset is already present in the cache instead of making a ride to the server. Service workers are also used for sending web-push notifications to the subscribed devices.

    We’ve to send web-push notifications for the stock price updates to the subscribed users. Let’s set the ground and build this service worker file!

    The insertSubscription related snipped in the index.js file is doing the work of registering service worker and putting the subscription object in the database using subscriptionMutation.

    Please refer queries.js for all the queries and mutations being used in the project.

    serviceWorker.register(insertSubscription); invokes the register function written in the serviceWorker.js file. Here it is:

    export const register = (insertSubscription) => {
      if ('serviceWorker' in navigator) {
        const swUrl = `${process.env.PUBLIC_URL}/serviceWorker.js`
        navigator.serviceWorker.register(swUrl)
          .then(() => {
            console.log('Service Worker registered');
            return navigator.serviceWorker.ready;
          })
          .then((serviceWorkerRegistration) => {
            getSubscription(serviceWorkerRegistration, insertSubscription);
            Notification.requestPermission();
          })
      }
    }

    The above function first checks if serviceWorker is supported by the browser and then registers the service worker file hosted on the URL swUrl. We’ll check this file in a moment!

    The getSubscription function does the work of getting the subscription object using the subscribe method on the pushManager object. This subscription object is then stored in the user_subscription table against a userId. Please note that the userId is being generated using the uuid function. Let’s check out the getSubscription function:

    const getSubscription = (serviceWorkerRegistration, insertSubscription) => {
      serviceWorkerRegistration.pushManager.getSubscription()
        .then ((subscription) => {
          const userId = uuidv4();
          if (!subscription) {
            const applicationServerKey = urlB64ToUint8Array('<APPLICATION_SERVER_KEY>')
            serviceWorkerRegistration.pushManager.subscribe({
              userVisibleOnly: true,
              applicationServerKey
            }).then (subscription => {
              insertSubscription({
                variables: {
                  userId,
                  subscription
                }
              });
              localStorage.setItem('serviceWorkerRegistration', JSON.stringify({
                userId,
                subscription
              }));
            })
          }
        })
    }

    You can check serviceWorker.js file for the complete code!

    Notification Popup
    Notification Popup. (Large preview)

    Notification.requestPermission() invoked this popup that asks the user for the permission for sending notifications. Once the user clicks on Allow, a subscription object is generated by the push service. We’re storing that object in the localStorage as:

    Webpush Subscriptions object
    Webpush Subscriptions object. (Large preview)

    The field endpoint in the above object is used for identifying the device and the server uses this endpoint to send web push notifications to the user.

    We have done the work of initializing and registering the service worker. We also have the subscription object of the user! This is working all good because of the serviceWorker.js file present in the public folder. Let’s now set up the service worker to get things ready!

    This is a bit difficult topic but let’s get it right! As mentioned earlier, the create-react-app utility doesn’t support customizations by default for the service worker. We can achieve customer service worker implementation using workbox-build module.

    We also have to make sure that the default behavior of pre-caching files is intact. We’ll modify the part where the service worker gets build in the project. And, workbox-build helps in achieving exactly that! Neat stuff! Let’s keep it simple and list down all that we have to do to make the custom service worker work:

    • Handle the pre-caching of assets using workboxBuild.
    • Create a service worker template for caching assets.
    • Create sw-precache-config.js file to provide custom configuration options.
    • Add the build service worker script in the build step in package.json.

    Don’t worry if all this sounds confusing! The article doesn’t focus on explaining the semantics behind each of these points. We’ve to focus on the implementation part for now! I’ll try to cover the reasoning behind doing all the work to make a custom service worker in another article.

    Let’s create two files sw-build.js and sw-custom.js in the src directory. Please refer to the links to these files and add the code to your project.

    Let’s now create sw-precache-config.js file at the root level and add the following code in that file:

    module.exports = {
      staticFileGlobs: [
        'build/static/css/**.css',
        'build/static/js/**.js',
        'build/index.html'
      ],
      swFilePath: './build/serviceWorker.js',
      stripPrefix: 'build/',
      handleFetch: false,
      runtimeCaching: [{
        urlPattern: /this\.is\.a\.regex/,
        handler: 'networkFirst'
      }]
    }

    Let’s also modify the package.json file to make room for building the custom service worker file:

    Add these statements in the scripts section:

    "build-sw": "node ./src/sw-build.js",
    "clean-cra-sw": "rm -f build/precache-manifest.*.js && rm -f build/service-worker.js",

    And modify the build script as:

    "build": "react-scripts build && npm run build-sw && npm run clean-cra-sw",

    The setup is finally done! We now have to add a custom service worker file inside the public folder:

    function showNotification (event) {
      const eventData = event.data.json();
      const { title, body } = eventData
      self.registration.showNotification(title, { body });
    }
    
    self.addEventListener('push', (event) => {
      event.waitUntil(showNotification(event));
    })

    We’ve just added one push listener to listen to push-notifications being sent by the server. The function showNotification is used for displaying web push notifications to the user.

    This is it! We’re done with all the hard work of setting up a custom service worker to handle web push notifications. We’ll see these notifications in action once we build the user interfaces!

    We’re getting closer to building the main code pieces. Let’s now start with the first view!

    Symbol List View

    The App component being used in the previous section looks like this:

    import React from 'react';
    import SymbolList from './views/symbolList';
    
    const App = () => {
      return <SymbolList />;
    };
    
    export default App;

    It is a simple component that returns SymbolList view and SymbolList does all the heavy-lifting of displaying symbols in a neatly tied user interface.

    Let’s look at symbolList.js inside the views folder:

    Please refer to the file here!

    The component returns the results of the renderSymbols function. And, this data is being fetched from the database using the useQuery hook as:

    const { loading, error, data } = useQuery(symbolsQuery, {variables: { userId }});

    The symbolsQuery is defined as:

    export const symbolsQuery = gql`
      query getSymbols($userId: uuid) {
        symbol {
          id
          company
          symbol_events(where: {user_id: {_eq: $userId}}) {
            id
            symbol
            trigger_type
            trigger_value
            user_id
          }
          stock_symbol_aggregate {
            aggregate {
              max {
                high
                volume
              }
              min {
                low
                volume
              }
            }
          }
        }
      }
    `;

    It takes in userId and fetches the subscribed events of that particular user to display the correct state of the notification icon (bell icon that is being displayed along with the title). The query also fetches the max and min values of the stock. Notice the use of aggregate in the above query. Hasura’s Aggregation queries do the work behind the scenes to fetch the aggregate values like count, sum, avg, max, min, etc.

    Based on the response from the above GraphQL call, here’s the list of cards that are displayed on the front-end:

    Stock Cards
    Stock Cards. (Large preview)

    The card HTML structure looks something like this:

    <div key={id}>
      <div className="card-container">
        <Card>
          <CardBody>
            <CardTitle className="card-title">
              <span className="company-name">{company}  </span>
                <Badge color="dark" pill>{id}</Badge>
                <div className={classNames({'bell': true, 'disabled': isSubscribed})} id={`subscribePopover-${id}`}>
                  <FontAwesomeIcon icon={faBell} title="Subscribe" />
                </div>
            </CardTitle>
            <div className="metrics">
              <div className="metrics-row">
                <span className="metrics-row--label">High:</span> 
                <span className="metrics-row--value">{max.high}</span>
                <span className="metrics-row--label">{' '}(Volume: </span> 
                <span className="metrics-row--value">{max.volume}</span>)
              </div>
              <div className="metrics-row">
                <span className="metrics-row--label">Low: </span>
                <span className="metrics-row--value">{min.low}</span>
                <span className="metrics-row--label">{' '}(Volume: </span>
                <span className="metrics-row--value">{min.volume}</span>)
              </div>
            </div>
            <Button className="timeseries-btn" outline onClick={() => toggleTimeseries(id)}>Timeseries</Button>{' '}
          </CardBody>
        </Card>
        <Popover
          className="popover-custom" 
          placement="bottom" 
          target={`subscribePopover-${id}`}
          isOpen={isSubscribePopoverOpen === id}
          toggle={() => setSubscribeValues(id, symbolTriggerData)}
        >
          <PopoverHeader>
            Notification Options
            <span className="popover-close">
              <FontAwesomeIcon 
                icon={faTimes} 
                onClick={() => handlePopoverToggle(null)}
              />
            </span>
          </PopoverHeader>
          {renderSubscribeOptions(id, isSubscribed, symbolTriggerData)}
        </Popover>
      </div>
      <Collapse isOpen={expandedStockId === id}>
        {
          isOpen(id) ? <StockTimeseries symbol={id}/> : null
        }
      </Collapse>
    </div>

    We’re using the Card component of ReactStrap to render these cards. The Popover component is used for displaying the subscription-based options:

    Notification Options
    Notification Options. (Large preview)

    When the user clicks on the bell icon for a particular stock, he can opt-in to get notified every hour or when the price of the stock has reached the entered value. We’ll see this in action in the Events/Time Triggers section.

    Note: We’ll get to the StockTimeseries component in the next section!

    Please refer to symbolList.js for the complete code related to the stocks list component.

    Stock Timeseries View

    The StockTimeseries component uses the query stocksDataQuery:

    export const stocksDataQuery = gql`
      query getStocksData($symbol: String) {
        stock_data(order_by: {time: desc}, where: {symbol: {_eq: $symbol}}, limit: 25) {
          high
          low
          open
          close
          volume
          time
        }
      }
    `;

    The above query fetches the recent 25 data points of the selected stock. For example, here is the chart for the Facebook stock open metric:

    Stock Prices timeline
    Stock Prices timeline. (Large preview)

    This is a straightforward component where we pass in some chart options to [HighchartsReact] component. Here are the chart options:

    const chartOptions = {
      title: {
        text: `${symbol} Timeseries`
      },
      subtitle: {
        text: 'Intraday (5min) open, high, low, close prices & volume'
      },
      yAxis: {
        title: {
          text: '#'
        }
      },
      xAxis: {
        title: {
          text: 'Time'
        },
        categories: getDataPoints('time')
      },
      legend: {
        layout: 'vertical',
        align: 'right',
        verticalAlign: 'middle'
      },
      series: [
        {
          name: 'high',
          data: getDataPoints('high')
        }, {
          name: 'low',
          data: getDataPoints('low')
        }, {
          name: 'open',
          data: getDataPoints('open')
        },
        {
          name: 'close',
          data: getDataPoints('close')
        },
        {
          name: 'volume',
          data: getDataPoints('volume')
        }
      ]
    }

    The X-Axis shows the time and the Y-Axis shows the metric value at that time. The function getDataPoints is used for generating a series of points for each of the series.

    const getDataPoints = (type) => {
      const values = [];
      data.stock_data.map((dataPoint) => {
        let value = dataPoint[type];
        if (type === 'time') {
          value = new Date(dataPoint['time']).toLocaleString('en-US');
        }
        values.push(value);
      });
      return values;
    }

    Simple! That’s how the Chart component is generated! Please refer to Chart.js and stockTimeseries.js files for the complete code on stock time-series.

    You should now be ready with the data and the user interfaces part of the project. Let’s now move onto the interesting part — setting up event/time triggers based on the user’s input.

    Setting Up Event/Scheduled Triggers

    In this section, we’ll learn how to set up triggers on the Hasura console and how to send web push notifications to the selected users. Let’s get started!

    Events Triggers On Hasura Console

    Let’s create an event trigger stock_value on the table stock_data and insert as the trigger operation. The webhook will run every time there is an insert in the stock_data table.

    Event triggers setup
    Event triggers setup. (Large preview)

    We’re going to create a glitch project for the webhook URL. Let me put down a bit about webhooks to make easy clear to understand:

    Webhooks are used for sending data from one application to another on the occurrence of a particular event. When an event is triggered, an HTTP POST call is made to the webhook URL with the event data as the payload.

    In this case, when there is an insert operation on the stock_data table, an HTTP post call will be made to the configured webhook URL (post call in the glitch project).

    Glitch Project For Sending Web-push Notifications

    We’ve to get the webhook URL to put in the above event trigger interface. Go to glitch.com and create a new project. In this project, we’ll set up an express listener and there will be an HTTP post listener. The HTTP POST payload will have all the details of the stock datapoint including open, close, high, low, volume, time. We’ll have to fetch the list of users subscribed to this stock with the value equal to the close metric.

    These users will then be notified of the stock price via web-push notifications.

    That’s all we’ve to do to achieve the desired target of notifying users when the stock price reaches the expected value!

    Let’s break this down into smaller steps and implement them!

    Installing Dependencies

    We would need the following dependencies:

    • express: is used for creating an express server.
    • apollo-fetch: is used for creating a fetch function for getting data from the GraphQL endpoint.
    • web-push: is used for sending web push notifications.

    Please write this script in package.json to run index.js on npm start command:

    "scripts": {
      "start": "node index.js"
    }
    Setting Up Express Server

    Let’s create an index.js file as:

    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    app.use(bodyParser.json());
    
    const handleStockValueTrigger = (eventData, res) => {
      /* Code for handling this trigger */
    }
    
    app.post('/', (req, res) => {
      const { body } = req
      const eventType = body.trigger.name
      const eventData = body.event
      
      switch (eventType) {
        case 'stock-value-trigger':
          return handleStockValueTrigger(eventData, res);
      }
      
    });
    
    app.get('/', function (req, res) {
      res.send('Hello World - For Event Triggers, try a POST request?');
    });
    
    var server = app.listen(process.env.PORT, function () {
        console.log(`server listening on port ${process.env.PORT}`);
    });
    

    In the above code, we’ve created post and get listeners on the route /. get is simple to get around! We’re mainly interested in the post call. If the eventType is stock-value-trigger, we’ll have to handle this trigger by notifying the subscribed users. Let’s add that bit and complete this function!

    Fetching Subscribed Users
    const fetch = createApolloFetch({
      uri: process.env.GRAPHQL_URL
    });
    
    const getSubscribedUsers = (symbol, triggerValue) => {
      return fetch({
        query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) {
          events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) {
            user_id
            user_subscription {
              subscription
            }
          }
        }`,
        variables: {
          symbol,
          triggerValue
        }
      }).then(response => response.data.events)
    }
    
    
    const handleStockValueTrigger = async (eventData, res) => {
      const symbol = eventData.data.new.symbol;
      const triggerValue = eventData.data.new.close;
      const subscribedUsers = await getSubscribedUsers(symbol, triggerValue);
      const webpushPayload = {
        title: `${symbol} - Stock Update`,
        body: `The price of this stock is ${triggerValue}`
      }
      subscribedUsers.map((data) => {
        sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload));
      })
      res.json(eventData.toString());
    }
    

    In the above handleStockValueTrigger function, we’re first fetching the subscribed users using the getSubscribedUsers function. We’re then sending web-push notifications to each of these users. The function sendWebpush is used for sending the notification. We’ll look at the web-push implementation in a moment.

    The function getSubscribedUsers uses the query:

    query getSubscribedUsers($symbol: String, $triggerValue: numeric) {
      events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) {
        user_id
        user_subscription {
          subscription
        }
      }
    }

    This query takes in the stock symbol and the value and fetches the user details including user-id and user_subscription that matches these conditions:

    • symbol equal to the one being passed in the payload.
    • trigger_type is equal to event.
    • trigger_value is greater than or equal to the one being passed to this function (close in this case).

    Once we get the list of users, the only thing that remains is sending web-push notifications to them! Let’s do that right away!

    Sending Web-Push Notifications To The Subscribed Users

    We’ve to first get the public and the private VAPID keys to send web-push notifications. Please store these keys in the .env file and set these details in index.js as:

    webPush.setVapidDetails(
      'mailto:<YOUR_MAIL_ID>',
      process.env.PUBLIC_VAPID_KEY,
      process.env.PRIVATE_VAPID_KEY
    );
    
    const sendWebpush = (subscription, webpushPayload) => {
      webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err))
    }

    The sendNotification function is used for sending the web-push on the subscription endpoint provided as the first parameter.

    That’s all is required to successfully send web-push notifications to the subscribed users. Here’s the complete code defined in index.js:

    const express = require('express');
    const bodyParser = require('body-parser');
    const { createApolloFetch } = require('apollo-fetch');
    const webPush = require('web-push');
    
    webPush.setVapidDetails(
      'mailto:<YOUR_MAIL_ID>',
      process.env.PUBLIC_VAPID_KEY,
      process.env.PRIVATE_VAPID_KEY
    );
    
    const app = express();
    app.use(bodyParser.json());
    
    const fetch = createApolloFetch({
      uri: process.env.GRAPHQL_URL
    });
    
    const getSubscribedUsers = (symbol, triggerValue) => {
      return fetch({
        query: `query getSubscribedUsers($symbol: String, $triggerValue: numeric) {
          events(where: {symbol: {_eq: $symbol}, trigger_type: {_eq: "event"}, trigger_value: {_gte: $triggerValue}}) {
            user_id
            user_subscription {
              subscription
            }
          }
        }`,
        variables: {
          symbol,
          triggerValue
        }
      }).then(response => response.data.events)
    }
    
    const sendWebpush = (subscription, webpushPayload) => {
      webPush.sendNotification(subscription, webpushPayload).catch(err => console.log('error while sending webpush', err))
    }
    
    const handleStockValueTrigger = async (eventData, res) => {
      const symbol = eventData.data.new.symbol;
      const triggerValue = eventData.data.new.close;
      const subscribedUsers = await getSubscribedUsers(symbol, triggerValue);
      const webpushPayload = {
        title: `${symbol} - Stock Update`,
        body: `The price of this stock is ${triggerValue}`
      }
      subscribedUsers.map((data) => {
        sendWebpush(data.user_subscription.subscription, JSON.stringify(webpushPayload));
      })
      res.json(eventData.toString());
    }
    
    app.post('/', (req, res) => {
      const { body } = req
      const eventType = body.trigger.name
      const eventData = body.event
      
      switch (eventType) {
        case 'stock-value-trigger':
          return handleStockValueTrigger(eventData, res);
      }
      
    });
    
    app.get('/', function (req, res) {
      res.send('Hello World - For Event Triggers, try a POST request?');
    });
    
    var server = app.listen(process.env.PORT, function () {
        console.log("server listening");
    });

    Let’s test out this flow by subscribing to stock with some value and manually inserting that value in the table (for testing)!

    I subscribed to AMZN with value as 2000 and then inserted a data point in the table with this value. Here’s how the stocks notifier app notified me right after the insertion:

    Inserting a row in stock_data table for testing
    Inserting a row in stock_data table for testing. (Large preview)

    Neat! You can also check the event invocation log here:

    Event Log
    Event Log. (Large preview)

    The webhook is doing the work as expected! We’re all set for the event triggers now!

    Scheduled/Cron Triggers

    We can achieve a time-based trigger for notifying the subscriber users every hour using the Cron event trigger as:

    Cron/Scheduled Trigger setup
    Cron/Scheduled Trigger setup. (Large preview)

    We can use the same webhook URL and handle the subscribed users based on the trigger event type as stock_price_time_based_trigger. The implementation is similar to the event-based trigger.

    Conclusion

    In this article, we built a stock price notifier application. We learned how to fetch prices using the Alpha Vantage APIs and store the data points in the Hasura backed Postgres database. We also learned how to set up the Hasura GraphQL engine and create event-based and scheduled triggers. We built a glitch project for sending web-push notifications to the subscribed users.

    Smashing Editorial
    (ra, yk, il)

    Source link

    web design

    Authenticating React Apps With Auth0 — Smashing Magazine

    11/11/2020

    About The Author

    Nefe is a Frontend Developer who enjoys learning new things and sharing his knowledge with others.
    More about
    Nefe

    An important aspect of app development is ensuring that only verified users have access to our apps. This can be tedious and costly do, especially when you add alternative methods of logging in outside emails and passwords. Auth0 is a service that provides authentication functionalities to developers out of the box.

    In this article, we’ll learn how to authenticate our React apps using Auth0. We will also learn how to set up Social Logins in our apps. This article will be beneficial to readers who want to add some form of authentication to their apps or want to get familiar with Auth0.

    Authentication is a critical aspect of most apps, as developers must ensure the apps they build are secure and can only be accessed by verified users. While custom authentication solutions can be built, the cost and resources involved to build, maintain, host, and secure them can be heavy. This is where Auth0 comes in.

    Auth0 provides SDKs for all popular web, mobile, and native platforms, allowing for deep integration with the language and stack of your preference. You can also set up different login options so your users can login to your app with their preferred method.

    This article does not cover an in-depth explanation of how authentication works under the hood. Auth0 has a resource that covers that.

    Note: To follow along, you’ll need a basic understanding of React and React Hooks.

    What Is Auth0?

    Auth0 is a flexible solution to add authentication and authorization to your apps. You can connect any app to Auth0 and define the identity providers you want to use, whether Google, Facebook, Github or others. Whenever a user logs into your app, Auth0 will verify their identity and send the authentication data back to your app.

    While Auth0 comes with with different login forms, their Universal Login is the safest and faster to get started with. Auth0 also recommends you use this. With Universal Login, the user is redirected to the login page, authenticated by Auth0’s servers, and then they are redirected back to your app. When using Universal Login, you can start off using a simple username and password, and later on, add other login methods, based on your app’s requirements.

    Another benefit of using Universal Login is that you don’t need to set up a custom login page. However, you can customize the Universal Login to suit your needs.

    How Does Auth0 Work?

    When Auth0’s servers redirect a user back to your app, the redirect URL is populated with information about the authenticated user. This allows us to access data about the user from the information we get back from the identity provider. A user profile in Auth0 is the information obtained from an identity provider. The user data we get back will differ from one identity provider to another.

    When the user is redirected back to the app, the information sent along in the redirect URL is as follows:

    • access token
      This is used to inform an API that the bearer of the token is authorized to access the API and perform some action. Access tokens are not intended to carry information about the user. They are only used to authorize access to a resource.
    • id token
      This is a security token granted by the OpenID Provider that contains information about a user. This information tells your client app that the user is authenticated, and can also give you information like their username. It comes in JSON Web Token (JWT) format.
    • expires in
      This tells us how many seconds until the access token is no longer valid. By default, this is 1200 seconds (20 minutes). When the access token expires, the app will be forced to make the user sign in again.
    • scope
      OpenID Connect (OIDC) scopes are used by an app during authentication to authorize access to a user’s details, like name and picture. Each scope returns a set of user attributes, which are called claims. The scopes an app should request depend on which user attributes the app needs. Once the user authorizes the requested scopes, the claims are returned in an ID Token and are also available through the /userinfo endpoint.

    Auth0 Authentication Methods

    Auth0 provides several platform integrations. In this article, we will take a look at the JavaScript SDK and the React SDK.

    • JavaScript SDK: This is a client-side JavaScript toolkit for Auth0 API.
    • React SDK: The Auth0 React SDK (auth0-react.js) is a JavaScript library for implementing authentication and authorization in React apps with Auth0.

    Configuring Your Auth0 App

    Your Auth0 dashboard
    Your Auth0 dashboard. (Large preview)
    • Select the type of app. Ours is a SPA.
    Choose app type
    Choose app type. (Large preview)
    Choose technology
    Choose technology. (Large preview)
    • Take note of your app credentials. We’ll need them to integrate Auth0 into our react app.
    app credentials
    App credentials. (Large preview)

    We configure the URLs of the app in its settings for the login and logout functionality to work properly.

    A callback URL is a URL in your app where Auth0 redirects the user after they have authenticated. For our app, set the Allowed Callback URL to http://localhost:3000.

    After Auth0 logs the user out of the authorization server, the logout URL is the URL the user is redirected to. We also set this to http://localhost:3000. Callback URLs can be manipulated by unauthorized parties, so Auth0 recognizes only URLs in the Allowed Callback URLs field of an app’s Settings as valid.

    Allowed Web Origins handles checking for current authentication sessions. This ensures the user login persists when they leave your app or refresh the page. We also set this to http://localhost:3000.

    Authentication Using Auth0 JavaScript SDK

    Let’s use this SDK to simulate a basic Auth0 login flow. The source code for this section is available on GitHub. The components of this demo app are:

    • App.js: this is the root component. We pass the Auth class we will create later to each component from here.
    • Nav.js: this will contain the login and logout buttons, helping the user properly navigate from one page to another.
    • Profile.js: the user profile. It will only be accessible if the user has logged into the app.
    • Home.js: the Home component.
    • Auth.js: we define the authentication utilities here in an Auth class we will define.
    • Callback.js: the component Auth0 redirects the user to once they login.

    Let’s set up our app’s credentials as environment variables.

    REACT_APP_AUTH0_DOMAIN=your-domain
    REACT_APP_AUTH0_CLIENTID=your-client-id
    REACT_APP_AUTH0_CALLBACK_URL=your-callback-url

    Create a .env to store the domain and cleintId credentials of your app. Also, set the callback URL in the file. In this app, I will be using http://localhost:3000 as my callback URL.

    Adding Auth0 Instance

    npm i auth0-js
    import auth0 from 'auth0-js';

    To use the JavaScript SDK in our app we, first install the SDK. Next, we create an Auth.js file where we set up the authentication functionality. Import auth0 from auth0-js into the Auth.js file.

    export default class Auth {
      constructor(history){
        this.history = history;
        this.auth0 = new auth0.WebAuth({
          domain: process.env.REACT_APP_AUTH0_DOMAIN,
          clientID: process.env.REACT_APP_AUTH0_CLIENTID,
          redirectUri: process.env.REACT_APP_AUTH0_CALLBACK_URL,
          responseType: "token id_token",
          scope: "openid profile email"
        })
    }

    Next, we initialize a new instance of the Auth0 app. To do this, create a class called Auth. Here, we initialize a new Auth0 instance. We pass in an options object that contains some parameters.

    There are several parameters we can add to the Auth0 instance, and of those parameters, only the domain and clientID are required.

    • domain: your Auth0 account domain.
    • clientID: your Auth0 client ID.
    • redirectUri: the URL Auth0 redirects your user when they have been authenticated. By default the URL you specified for your app’s Callback URL will be used, so this parameter is not required.
    • responseType: we define the response we want to get back from Auth0 when it authenticates our user. We specify that we want to get the id_token back from the response.
    • scope: we define what information we want to get from the user. This way, we will be able to access their email address and whatever information is stored in their profile. The information we will be able to get from the user depends on the identity provider they use to sign in. We will be making use of the OpenID Connect protocol to access information about the user.

    The Auth class accepts react-router’s history prop as an argument. Later on, we will use this to redirect the user to different pages in our app.

    We create a new instance of auth0 and pass in the configurations. We assign the new instance to this.auth0. We get values of domain, clientID and redirectUri are from the .env file we created earlier.

    Adding Login Functionality

    We need to add a login method to the class we created in Auth.js.

    login = () => {
      this.auth0.authorize()
    }

    To do that, we add Auth0’s authorize() method to login. authorize() is used for logging in users through the Universal Login. When authorize() is called, it redirects the user to Auth0’s login page.

    The Auth class needs to the passed to other components, the Nav, Home and Callback components.

    import Auth from './Auth';
    
    function App({history}) {
      const auth = new Auth(history) 
      return (
        <div className="App">
          <Nav auth={auth}/>
          <Switch>
            <div className="body">
              <Route exact path="/" render={props => <Home auth={auth} {...props} />} />
              <Route exact path="/callback" render={props => <Callback auth={auth} {...props} />} />
              <Route exact path="/profile" render={props => <Profile auth={auth} {...props} />} /> 
            </div>
          </Switch>
        </div>
      );
    }
    
    export default withRouter(App);

    Here, we create a new instance of the Auth class and pass it to the components that need it as a prop.

    Since the Auth class needs history, we’ll make use of withRouter so we can be able to access history.

    import { Link } from 'react-router-dom' 
    
    const Nav = ({auth}) => {
      return (
        <nav>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li>
              <button onClick={auth.login}>log in</button>
            </li>
          </ul>
        </nav>
      )
    }
    export default Nav

    Now that we have defined the login() method, we can use it in the login button. The user will be redirected to Auth0’s login page and then to the callback URL once they have been authenticated.

    Next, we have to create the component the user gets redirected to once they log in.

    import React from 'react'
    
    const Callback = () => {
      return (
        <div>
          <h1>I am the callback component</h1>
        </div>
      )
    }
    export default Callback

    Create a Callback.js file, and set up a Callback component in it. Now when the user logs in, they are redirected to the Callback component.

    Handling Authentication

    When Auth0 redirects the user back to the app, it sends along some authentication data in the callback URL. This data contains encoded information about the authenticated user.
    To access the data Auth0 sends back in the redirect URL, we set up a handleAuth() method in the Auth class. This method will be called in the Callback component.

    handleAuth = () => {
        this.auth0.parseHash((err, authResult) => {
          if(authResult && authResult.accessToken && authResult.idToken) {
            this.setSession(authResult);
            this.history.push("/");
          } else if (err) {
            alert(`Error: ${err.error}`)
            console.log(err);  
          }
        })
    }

    After the user is redirected, we can use the parseHash method to parse the information that is sent back along in the callback URL. After parsing, we get back an error object and an authResult. We check to see if there is an authResult, and an accessToken and idToken. If true, we pass in the authResult to the setSession method and redirect the user to the homepage.

    We will use setSession() to create a session for the authenticated user and store the authentication data in local storage later on. If there are any errors, we use the alert method to show them and also log the error object to the console.

    We call the handleAuth() method we defined above in the useEffect whenever Callback mounts, that is, when the user gets redirected after logging in.

    import React, {useEffect} from 'react'
    const Callback = ({auth}) => {
      useEffect(() => {
        auth.handleAuth()
      }, [])
    
      return (
        <div>
          <h1>I am the callback component</h1>
        </div>
      )
    }
    export default Callback
    

    We do this because when Auth0 redirects the user to the Callback component, we want to be able to access the response data it sends along in the redirect URL, and the handleAuth() method is where we call Auth0’s parseHash method. So when the component mounts, we call handleAuth() in the useEffect.

    Tracking Authentication State

    We don’t want the profile page to be accessible if a user has not logged in. We need to be able to check if the user is authenticated and then give them access to the profile page. We can make use of the setSession() method we called in the handleAuth() method we have in the Auth class.

    setSession = authResult => {
        //set the time the access token will expire
        const expiresAt = JSON.stringify(
          authResult.expiresIn * 1000 + new Date().getTime()
        )
    
        localStorage.setItem("access_token", authResult.accessToken)
        localStorage.setItem("id_token", authResult.idToken)
        localStorage.setItem("expires_at", expiresAt)
    }

    In setSession() we add an expiresAt variable to hold the time the access token will expire. expiresIn is a string containing the expiration time (in seconds) of the accessToken. We convert the expiration time we get from expiresIn to Unix epoch time. Next, we save expiresAt, and the authResult’s accessToken and idToken to local storage.

    The next step in setting up a tracker for the authentication state is to create an isAuthenticated method.

    isAuthenticated = () => { 
        const expiresAt =JSON.parse(localStorage.getItem("expires_at"));
        return new Date().getTime() < expiresAt;
    }

    In the method above, we parse the expires_at value that we saved to local storage and check if the current time is less than the time the token expires. If true, then the user is authenticated.

    Now that we can track the isAuthenticated state, we can use it in our app. Let’s use it in the Nav.js file.

    import React from 'react';
    import { Link } from 'react-router-dom' 
    
    const Nav = ({auth}) => {
      return (
        <nav>
          <ul>
            <li><Link to="/">Home</Link></li>
            <li>
              <button onClick={auth.isAuthenticated() ? auth.logout : auth.login}>
                {auth.isAuthenticated() ? "log out" : "log in"}
               </button>
            </li>
          </ul>
        </nav>
      )
    }
    
    export default Nav

    Instead of hard-coding a login button and using the login() method, we dynamically render either the login button with the login() method or the logout button with the logout() method based on the isAuthenticated state. In the Nav component we make use of a ternary operator to determine the text that gets displayed on the button and the method that gets called when the user clicks the button. The displayed text and called method is dependent on the value of auth.isAuthenticated().

    Now we can go ahead to implement the Home component.

    import {Link} from 'react-router-dom'
    const Home = ({auth}) => {
      return (
        <div>
          <h1>home</h1>
          {
            auth.isAuthenticated() && (
              <h4>
                You are logged in! You can now view your{' '}
                <Link to="/profile">profile</Link>
              </h4>
            )
            }
        </div>
      )
    }
    export default Home

    In the Home component above, we use the isAuthenticated state to dynamically display a link to the user’s profile if the user is logged in.

    We want to display information about a user when they login to the app. To do this, we have to create two method in the Auth class that will get that information.

    getAccessToken = () => {
        const accessToken = localStorage.getItem("access_token")
        if(!accessToken){
          throw new Error("No access token found")
        }
        return accessToken
    }

    The access token is required to get the user data. We create a getAccessToken() method that gets the access token from local storage. If there is no access token, we throw an error.

    The getProfile() method gets the user data for us and here is what it should look like.

    getProfile = callback => {
      this.auth0.client.userInfo(this.getAccessToken(), (err, profile) => {
        callback(profile);
      });
    }

    The getProfile() method calls the userInfo() method which will make a request to the /userinfo endpoint and return the user object, which contains the user information. The access token is required for the /userinfo endpoint, so we pass getAccessToken() as an argument.

    The user profile information included in the response depends on the scopes we set. Earlier on, we set the scope for our app to profile and email, so those are the only pieces of information about the user we will get back.

    Let us set up the Profile component.

    import React, { useEffect, useState } from "react";
    
    const Profile = ({ auth }) => {
      const [profile, setProfile] = useState(null);
      useEffect(() => {
        auth.getProfile((profile) => {
          setProfile(profile);
        });
      }, [auth]);
    
      if (!profile) {
        return <h1>Loading...</h1>;
      }
    
      return (
        <div>
          <h1>profile</h1>
          <>
            <p>{profile.name}</p>
            <p>{profile.nickname}</p>
            <img src={profile.picture} />
            <pre>{JSON.stringify(profile, null, 2)}</pre>
          </>
        </div>
      );
    };
    export default Profile;

    In Profile.js, we create a profile state, and in the useEffect we call the getProfile method to access the user’s profile. Then we display the user data we get from the profile state.

    Adding Logout Functionality

    We define a logout() method in the Auth class.

    logout = () => {
        localStorage.removeItem("access_token")
        localStorage.removeItem("id_token")
        localStorage.removeItem("expires_at")
        this.auth0.logout({
          clientID: process.env.REACT_APP_AUTH0_CLIENTID,
          returnTo: "http://localhost:3000"
        });
    }

    Here, we remove the authResult, accessToken, and idToken we previously stored in the local storage. Then we direct the user to the homepage.

    To log a user out from Auth0’s servers, use the Auth0 logout() method. This method accepts an options object that contains the clientID and a returnTo property. returnTo is where you specify the URL in your app the user should be redirected to once they logout. The returnTo URL that is provided must be listed in the app’s Allowed Logout URLs in the Auth0 dashboard.

    Authentication Using React SDK

    Unlike the JavaScript SDK, the React SDK is easier to use. The code for this section is available on GitHub.

    Let’s set it up in our app. The components of this demo app are:

    • App.js: this is the root component.
    • LoginButton.js: handles the login functionality.
    • LogoutButon.js: handles the logout functionality.
    • Navbar.js: this holds the logout and login buttons.
    • Profile.js: this will hold the information of the logged-in user.

    First, we install Auth0’s React SDK in our React app.

    npm install @auth0/auth0-react

    Similarly to how we set up using the JavaScript SDK, we set up the Auth0 credentials we need. We create a .env to store the domain and cleintId credentials of your app.

    import {Auth0Provider} from '@auth0/auth0-react';
    
    const domain = process.env.REACT_APP_AUTH0_DOMAIN
    const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID
    
    ReactDOM.render(
      <Auth0Provider
        domain={domain}
        clientId={clientId}
        redirectUri={window.location.origin}
      >
        <App />
      </Auth0Provider>,
      document.getElementById('root')
    );

    To use the SDK, we need to wrap our app in an Auth0Provider component. This will provide the React Context to the components that are inside your app. We also set a redirectUri, which is where Auth0 redirects the user to when they log in. Under the hood, the Auth0 React SDK uses React Context to manage the authentication state of your users.

    Setting Up Login

    Here, we set up the login button.

    import {useAuth0} from '@auth0/auth0-react';
    import {Button} from './Styles';
    
    const LoginButton = () => {
      const {loginWithPopup} = useAuth0()
     return(
       <Button onClick={() => loginWithPopup()}>
        Log in
       </Button>
     )
    }

    Auth0 provides us two ways of setting up login in our apps. We can use the loginWithPopup() or loginWithRedirect() methods. In this case, I used loginWithPopup().

    We destructure loginWithPopup() from the useAuth0 hook the SDK provides. Then we pass loginWithPopup() to the button’s onClick event. With that, we’ve set up the login button. If we had used loginWithRedirect(), the user would be redirected to Auth0 Login Page. Once the user has been authenticated, Auth0 redirects the back to your app.

    Setting Up Logout

    Let’s set up the logout functionality.

    import {Button} from './Styles';
    import {useAuth0} from '@auth0/auth0-react';
    
    const LogoutButton = () => {
      const {logout} = useAuth0()
      return(
        <Button onClick={() => logout()}>
          Log Out
        </Button>
     )
    }

    What we have here is similar to the login button setup. The only difference is that what we pulled out from the SDK is the logout function, and that is what we pass to the button’s onClick event.

    Calling logout() redirects your users to your Auth0 logout endpoint (https://YOUR_DOMAIN/v2/logout) and then immediately redirects them to the URL you specified in the Allowed Logout URLs filed of your app’s settings.

    Tracking Authentication State

    We want to conditionally render either the LogoutButton or the LoginButton based on the authentication state.

    import {StyledNavbar} from './Styles';
    import {useAuth0} from '@auth0/auth0-react';
    import LoginButton from './LoginButton';
    import LogoutButton from './LogoutButton';
    
    const Navbar = () => {
      const {isAuthenticated} = useAuth0()
      return (
        <StyledNavbar>
         { isAuthenticated ? <LogoutButton/> :  <LoginButton/> }  
        </StyledNavbar>
      )
    }

    We get isAuthenticated from useAuth0. isAuthenticated is a boolean that tells us if someone has signed in or not. In our Navbar, we use isAuthenticated to conditionally render the buttons. We do not have to go through the tedious process of setting up several custom methods just to track the authentication state as we did with the JavaScript SDK. The isAuthenticated boolean makes our lives easier.

    Displaying User Data

    We want to display the user’s data once they successfully login to our app.

    import {useAuth0} from '@auth0/auth0-react'
    import {ProfileBox, Image, P} from './Styles';
    
    const Profile = () => {
    const {user, isAuthenticated} = useAuth0()
     return(
      isAuthenticated && (<ProfileBox> 
        <Image src={user.picture} alt={user.name}/>
        <P>Name: {user.name}</P>
        <P>Username: {user.nickname}</P>
        <P>Email: {user.email}</P>
       </ProfileBox>)
     )
    }

    Once logged in, we have access to a user object, which we can get from useAuth0 and making it possible to access information about the user from the object. Here, we also get isAuthenticated from useAuth0 because we want to only display the data when a user is logged in.

    Unlike the JavaScript SDK where we had to use the getAccessToken() and getProfile() methods to access the user’s profile, we don’t have to do that with the React SDK.

    Adding Social Logins

    By default, Auth0 comes with Google login activated. However, you may want to give your user more options to login to your app. Let’s add Github Login to our app.

    • On your dashboard, go to the Connections tab and select Social. There, you’ll see the connections you have set up. Click on the Create Connection button. I have already enabled Github in my app, and that is why you see it here.
    Social Connections settings
    Social Connections settings. (Large preview)
    • Select the Github connection. We will get the clientID and the clientSecret from Github and put that into the social connection settings.
    Choose connection
    Choose connection. (Large preview)
    Github connection credentials
    Github connection credentials. (Large preview)
    Register a new 0Auth app
    Register a new 0Auth app. (Large preview)

    For the Homepage URL and the Authorization callback URL fields, you can use https://localhost:3000 or whatever URL your project needs.

    Next, pass the client ID and Secret into the Github connection in your Auth0 account. With that, you’ve set up Github login into your app.

    Conclusion

    In this article, we have seen how to authenticate our React apps using Auth0. We also went through the process of setting up Github social login in our app. Have fun adding authentication to your React app with Auth0.

    We have also seen how to authenticate our app with Auth0, and the developer-experience benefits of using the React SDK over the JavaScript SDK.

    Resources

    Smashing Editorial
    (ks, ra, yk, il)

    Source link

    web design

    A Dive Into React And Three.js Using react-three-fiber — Smashing Magazine

    11/09/2020

    About The Author

    Fortune Ikechi is a Frontend Engineer based in Rivers State Nigeria. He is a student of the University of Port-Harcourt. He is passionate about community and …
    More about
    Fortune

    react-three-fiber is a powerful Three.js renderer that helps render 3D models and animations for React and its native applications. In this tutorial, you will learn how to configure and build 3D models in a React application.

    Today, we’re going to learn how to configure and use react-three-fiber for building and displaying 3D models and animations in React and React Native applications.

    This tutorial is for developers who want to learn more about 3D model animations in the web using React and for anyone who’s had limitations with Three.js like inability to create canvas, bind user events like click events and start a render loop, react-three-fiber comes with these methods. We’ll be building a 3D model to better understand how to build Three.js 3D models using react-three-fiber.

    Getting Started With react-three-fiber

    Three.js is a library that makes it easier to create 3D graphics in the browser, it uses a canvas + WebGL to display the 3D models and animations, you can learn more here.

    react-three-fiber is a React renderer for Three.js on the web and react-native, it is a boost to the speed at which you create 3D models and animations with Three.js, some examples of sites with 3D models and animations can be found here. react-three-fiber reduces the time spent on animations because of its reusable components, binding events and render loop, first let’s take a look at what is Three.js.

    react-three-fiber allows us to build components of threeJS code using React state, hooks and props, it also comes with the following elements:

    mesh A property that helps define the shape of our models
    hooks react-three-fiber defines hooks that help us write functions that help define user events such as onClick and onPointOver
    Component-based and render loop react-three-fiber is component-based and renders according to a change in state or the store

    How To Use react-three-fiber

    To use react-three-fiber, you start by using the commands below:

    NPM

    npm i three react-three-fiber
    

    YARN

    yarn add three react-three-fiber 
    

    Note: For react-three-fiber to work, you’ll need to install three (Three.js) as we did above.

    Building A React 3D Ludo Dice Model And Animation Project

    Here we are going to build a 3D ludo dice model using react-three-fiber like we have in the video below.

    We will be using create-react-app to initialize our project, to do that let’s execute the command below on our terminal.

    create-react-app react-three-fiber-ludo-model
    

    The command above initializes a React project within our local machine, next let’s cd into the directory and install our packages react-three-fiber and three.

    cd react-three-fiber-ludo-model
    
    npm i three react-three-fiber
    

    Once, the packages are installed, let’s start our development server using the command

    npm start
    

    The above command should start our project development server in our browser. Next let’s open our project in our text editor of choice, inside our project src folder, delete the following files: App.css, App.test.js, serviceWorker.js and setupTests.js. Next, let’s delete all code that references the deleted files on our App.js.

    For this project, we will need a Box component for our ludo dices and our App component that’s given by React.

    Building The Box Component

    The Box component will contain the shape for our ludo dices, an image of a ludo dice and a state to always keep it in rotation. First, let’s import all the packages we need for our Box component below.

    import React, { useRef, useState, useMemo } from "react";
    import { Canvas, useFrame } from "react-three-fiber";
    import * as THREE from "three";
    import five from "./assets/five.png";
    

    In the above code, we are importing useRef, useState and useMemo. We’ll be using the useRef hook to access the mesh of the dice and the useState hook to check for the active state of the ludo dice. useMemo hook will be used to return the number on the dice. Next, we are importing Canvas and useFrame from react-three-fiber, the canvas is used to draw the graphics on the browser, while the useFrame allows components to hook into the render-loop, this makes it possible for one component to render over the content of another. Next, we imported the three package and then we imported a static image of a ludo dice.

    Next for us is to write logic for our Box component. First, we’ll start with building a functional component and add state to our component, let’s do that below.

    const Box = (props) => {
      const mesh = useRef();
    
      const [active, setActive] = useState(false);
    
      useFrame(() => {
        mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
      });
    
      const texture = useMemo(() => new THREE.TextureLoader().load(five), []);
      
      return (
        <Box />
      );
    }
    

    In the code above, we are creating a Box component with props, next we create a ref called mesh using the useRef hook, we did this so that we can always return the same mesh each time.

    A mesh is a visual element in a scene, its a 3D object that make up a triangular polygon, it’s usually built using a Geometry, which is used to define the shape of the model and Material which defines the appearance of the model, you can learn about a Mesh here, You can also learn more about the useRef hook here.

    After initializing a mesh, we need to initialize a state for our application using the useState hook, here we set up the hovered and active state of the application to false.

    Next, we use the useFrame hook from react-three-fiber to rotate the mesh (ludo dice), using the code below

    mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
    

    Here, we are rotating the current position of the mesh every 0.01 seconds, this is done to give the rotation a good animation.

    const texture = useMemo(() => new THREE.TextureLoader().load(five), []);
    

    In the code above, we are creating a constant called texture and passing in a react useMemo hook as a function to load a new dice roll, here the useMemo to memorize the dice image and its number. You can learn about the useMemo hook here.

    Next, we want to render the Box component on the browser and add our events, we do that below

    const Box = (props) => {
    return (
        <mesh
        {...props}
        ref={mesh}
        scale={active ? [2, 2, 2] : [1.5, 1.5, 1.5]}
        onClick={(e) => setActive(!active)}
          >
          <boxBufferGeometry args={[1, 1, 1]} />
          <meshBasicMaterial attach="material" transparent side={THREE.DoubleSide}>
            <primitive attach="map" object={texture} />
          </meshBasicMaterial>
        </mesh>
      );
    }
    

    In the code above, we are returning our Box component and wrapping it in the mesh we passed all the properties of the Box component using the spread operator, and then we referenced the mesh using the useRef hook. Next, we use the scale property from Three.js to set the size of the dice box when it’s active to 2 and 1.5 when it’s not. Last but not least, we added an onClick event to set state to active if it’s not set on default.

    <boxBufferGeometry args={[1, 1, 1]} />
    

    In order to render the dice box, we rendered the boxBufferGeometry component from Three.js, boxBufferGeometry helps us draw lines and point such as boxes, we used the args argument to pass constructors such as the size of box geometry.

    <meshBasicMaterial attach="material" transparent side={THREE.DoubleSide}>
    

    The meshBasicMaterial from Three.js, is used to draw geometries in a simple form. Here we passed the attach attribute and passing a THREE.DoubleSideprops to the side attribute. The THREE.DoubleSide defines the sides or spaces that should be rendered by react-three-fiber.

    <primitive attach="map" object={texture} />
    

    The primitive component from Three.js is used to draw 3D graphs. We attached the map property in order to maintain the original shape of the ludo dice. Next, we are going to render our Box component in the App.js file and complete our 3d ludo dice box. Your component should look similar to the image below.

    Box component for ludo 3D box

    Rendering 3D Ludo Dice Box

    In this section, we are going to render our Box component in our App.js and complete our 3d ludo box, to do that first, let’s create an App component and wrap it with a Canvas tag, this is to render our 3D models, let’s do that below.

    const App = () => {
      return (
        <Canvas>
        </Canvas>
      );
    }
    export default App;
    

    Next, let’s add a light to the boxes, react-three-fiber provides us with three components to light our models, they are as follows

    • ambientLight
      This is used to light all objects in a scene or model equally, it accepts props such as the intensity of the light, this will light the body of the ludo dice.
    • spotLight
      This light is emitted from a single direction, and it increases as the size of the object increases, this will light the points of the ludo dice.
    • pointLight
      This works similar to the light of a light bulb, light is emitted from a single point to all directions, this will be necessary for the active state of our application.

    Let’s implement the above on our application below.

    const App = () => {
      return (
        <Canvas>
          <ambientLight intensity={0.5} />
          <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
          <pointLight position={[-10, -10, -10]} />
        </Canvas>
      );
    }
    export default App;
    

    In the above code, we imported the ambientLight component from react-three-fiber and added an intensity of 0.5 to it, next we added a position and angle to our spotLight and pointLight component. The final step to our application is to render our box component and add a position to the ludo dice boxes, we’d do that in the code below

    <Box position={[-1.2, 0, 0]} />
    <Box position={[2.5, 0, 0]} />
    

    When done, your ludo 3D dice should look similar to the image below:

    3D ludo dice box

    A working demo is available on CodeSandbox.

    Conclusion

    react-three-fiber has made rendering 3D models and animations easier to create for React and React Native applications. By building our 3D ludo dice box, we’ve learned about the basics of Three.js alongside its components and benefits of react-three-fiber as well as how to use it.

    You can take this further by building 3D models and animations in your React and Native applications by using react-three-fiber on your own. I’d love to see what new things you come up with!

    You can read more on Three.js and react-three-fiber in the references below.

    Smashing Editorial
    (ks, ra, il)

    Source link

    web design

    Supercharge Testing React Applications With Wallaby.js — Smashing Magazine

    10/16/2020

    About The Author

    Kelvin is an independent software maker currently building Sailscasts — a platform to learn server-side JavaScript. He is also a technical writer and …
    More about
    Kelvin

    Ever had to switch your focus from your editor and to your terminal to see the results of your tests? This article will introduce you to Wallaby.js — a JavaScript productivity tool that supercharges your IDE by allowing you to get real-time feedback on your JavaScript tests in your code editor even before saving the file. You will also learn how to use Wallaby.js for testing React applications.

    Note: In order to be able to follow along, you’ll need to be familiar with JavaScript testing and have a working knowledge of building React applications.

    One thing you will discover very quickly when you start writing tests for an application is that you want to run your tests constantly when you are coding. Having to switch between your code editor and terminal window (or in the case of VS Code, the integrated terminal) adds an overhead and reduces your productivity as you build your application. In an ideal world, you would have instant feedback on your tests right in your editor as you are writing your code. Enter Wallaby.js.

    What Is Wallaby.js?

    Wallaby.js is an intelligent test runner for JavaScript that continuously runs your tests. It reports code coverage and other results directly to your code editor immediately as you change your code (even without saving the file). The tool is available as an editor extension for VS Code, IntelliJ Editors (such as WebStorm and IntelliJ IDEA), Atom, Sublime Text, and Visual Studio.

    A screenshot of Wallaby.js, an intelligent test runner for JavaScript that continuously runs your tests
    (Large preview)

    Why Wallaby.js?

    As stated earlier, Wallaby.js aims to improve your productivity in your day to day JavaScript development. Depending on your development workflow, Wallaby can save you hours of time each
    week by reducing context switching. Wallaby also provides code coverage reporting, error reporting, and other time-saving features such as time-travel debugging and test stories.

    Getting Started With Wallaby.js In VS Code

    Let’s see how we can get the benefits of Wallaby.js using VS Code.

    Note: If you are not using VS Code you can check out here for instructions on how to set up for other editors.

    Install The Wallaby.js VS Code Extension

    To get started we will install the Wallaby.js VS Code extension.

    After the extension is installed, the Wallaby.js core runtime will be automatically downloaded and installed.

    Wallaby License

    Wallaby provides an Open Source license for open source projects seeking to use Wallaby.js. Visit here to obtain an open-source license. You may use the open-source license with the demo repo for this article.

    You can also get a fully functional 15-day trial license by visiting here.

    If you want to use Wallaby.js on a non-open-source project beyond the 15-day trial license period, you may obtain a license key from the wallaby website.

    Add License Key To VS Code

    After obtaining a license key, head over to VS Code and in the command palette search for “Wallaby.js: Manage License Key”, click on the command and you will be presented with an input box to enter your license key, then hit enter and you will receive a notification that Wallaby.js has been successfully activated.

    Wallaby.js And React

    Now that we have Wallaby.js set up in our VS Code editor, let’s supercharge testing a React application with Wallaby.js.

    For our React app, we will add a simple upvote/downvote feature and we will write some tests for our new feature to see how Wallaby.js plays out in the mix.

    Creating The React App

    Note: You can clone the demo repo if you like, or you can follow along below.

    We will create our React app using the create-react-app CLI tool.

    npx create-react-app wallaby-js-demo
    

    Then open the newly scaffolded React project in VS Code.

    Open src/App.js and start Wallaby.js by running: “Wallaby.js: Start” in VS Code command palette (alternatively you can use the shortcut combo — Ctrl + Shift + R R if you are on a Windows or Linux machine, or Cmd + Shift + R R if you are on a Mac).

    A screenshot of create the React app by using the create-react-app CLI tool
    (Large preview)

    When Wallaby.js starts you should see its test coverage indicators to the left of your editor similar to the screenshot below:

    A screenshot of the App.js file showing test coverage indicators when starting Wallaby.js
    (Large preview)

    Wallaby.js provides 5 different colored indicators in the left margin of your code editor:

    1. Gray: means that the line of code is not executed by any of your tests.
    2. Yellow: means that some of the code on a given line was executed but other parts were not.
    3. Green: means that all of the code on a line was executed by your tests.
    4. Pink: means that the line of code is on the execution path of a failing test.
    5. Red: means that the line of code is the source of an error or failed expectation, or in the stack of an error.

    If you look at the status bar you will see Wallaby.js metrics for this file and it’s showing we have a 100% test coverage for src/App.js and a single passing test with no failing test. How does Wallaby.js know this? When we started Wallaby.js, it detected src/App.js has a test file src/App.test.js, it then runs those tests in the background for us and conveniently gives us the feedbacks using its color indicators and also giving us a summary metric on our tests in the status bar.

    When you also open src/App.test.js you will see similar feedback from Wallaby.js

    A screenshot of the code inside the App.test.js file
    (Large preview)

    Currently, all tests are passing at the moment so we get all green indicators. Let’s see how Wallaby.js handles failing tests. In src/App.test.js let’s make the test fail by changing the expectation of the test like so:

    // src/App.test.js
    expect(linkElement).not.toBeInTheDocument();
    

    The screenshot below shows how your editor would now look with src/App.test.js open:

    A screenshot of the App.test.js file opened in a editor showing failing tests
    (Large preview)

    You will see the indicators change to red and pink for the failing tests. Also notice we didn’t have to save the file for Wallaby.js to detect we made a change.

    You will also notice the line in your editor in src/App.test.js that outputs the error of the test. This is done thanks to Wallaby.js advanced logging. Using Wallaby.js advanced logging, you can also report and explore runtime values beside your code using console.log, a special comment format //? and the VS Code command, Wallaby.js: Show Value.

    Now let’s see the Wallaby.js workflow for fixing failing tests. Click on the Wallaby.js test indicator in the status bar to open the Wallaby.js output window. (“✗ 1 ✓ 0”)

    A screenshot of the App.test.js file open in an editor with the Wallaby.js Tests indicator tab open
    (Large preview)

    In the Wallaby.js output window, right next to the failing test, you should see a “Debug Test” link. Pressing Ctrl and clicking on that link will fire up the Wallaby.js time travel debugger. When we do that, the Wallaby.js Tools window will open to the side of your editor, and you should see the Wallaby.js debugger section as well as the Value explorer and Test file coverage sections.

    If you want to see the runtime value of a variable or expression, select the value in your editor and Wallaby.js will display it for you.

    A screenshot of the App.test.js file opened in an editor showing the runtime value selected
    (Large preview)

    Also, notice the “Open Test Story” link in the output window. Wallby.js test story allows you to see all your tests and the code they are testing in a single view in your editor.

    Let’s see this in action. Press Ctrl and click on the link — you should be able to see the Wallaby.js test story open up in your editor. Wallaby’s Test Story Viewer provides a unique and efficient way of inspecting what code your test is executing in a single logical view.

    A screenshot of what can be seen in the Test Story tab
    (Large preview)

    Another thing we will explore before fixing our failing test is the Wallaby.js app. Notice the link in the Wallaby.js output window: “Launch Coverage & Test Explorer”. Clicking on the link will launch the Wallaby.js app which will give you a compact birds-eye view of all tests in your project.

    Next, click on the link and start up the Wallaby.js app in your default browser via http://localhost:51245/. Wallaby.js will quickly detect that we have our demo project open in our editor which will then automatically load it into the app.

    Here is how the app should now look like:

    A screenshot of the Wallaby.js demo app project previewed in the browser
    (Large preview)

    You should be able to see the test’s metrics on the top part of the Wallaby.js app. By default, the Tests tab in the app is opened up. By clicking on the Files tab, you should be able to see the files in your project as well as their test coverage reports.

    A screenshot of a browser tab showing the demo preview of the Wallaby.js app and where the Files tab can be found
    (Large preview)

    Back on to the Tests tab, click on the test and you should see the Wallaby.js error reporting feature to the right:

    A screenshot showing how the app reports errors
    (Large preview)

    Now we’ve covered all that, go back to the editor, and fix the failing test to make Wallaby.js happy by reverting the line we changed earlier to this:

    expect(linkElement).toBeInTheDocument();
    

    The Wallaby.js output window should now look like the screenshot below and your test coverage indicators should be all passing now.

    A screenshot of the App.test.js file opened in an editor showing all tests passed in the Output tab
    (Large preview)

    Implementing Our Feature

    We’ve explored Wallaby.js in the default app created for us by create-react-app. Let’s implement our upvote/downvote feature and write tests for that.

    Our application UI should contain two buttons one for upvoting and the other for downvoting and a single counter that will be incremented or decremented depending on the button the user clicks. Let’s modify src/App.js to look like this.

    // src/App.js
    import React, { useState } from 'react';
    import logo from './logo.svg';
    import './App.css';
    
    function App() {
      const [vote, setVote] = useState(0);
    
      function upVote() {
        setVote(vote + 1);
      }
    
      function downVote() {
        // Note the error, we will fix this later...
        setVote(vote - 2);
      }
      return (
        <div className='App'>
          <header className='App-header'>
            <img src={logo} className='App-logo' alt='logo' />
            <p className='vote' title='vote count'>
              {vote}
            </p>
            <section className='votes'>
              <button title='upVote' onClick={upVote}>
                <span role='img' aria-label='Up vote'>
                  👍🏿
                </span>
              </button>
              <button title='downVote' onClick={downVote}>
                <span role='img' aria-label='Down vote'>
                  👎🏿
                </span>
              </button>
            </section>
          </header>
        </div>
      );
    }
    
    export default App;
    

    We will also style the UI just a bit. Add the following rules to src/index.css

    .votes {
      display: flex;
      justify-content: space-between;
    }
    p.vote {
      font-size: 4rem;
    }
    button {
      padding: 2rem 2rem;
      font-size: 2rem;
      border: 1px solid #fff;
      margin-left: 1rem;
      border-radius: 100%;
      transition: all 300ms;
      cursor: pointer;
    }
    
    button:focus,
    button:hover {
      outline: none;
      filter: brightness(40%);
    }
    

    If you look at src/App.js, you will notice some gray indicators from Wallaby.js hinting us that some part of our code isn’t tested yet. Also, you will notice our initial test in src/App.test.js is failing and the Wallaby.js status bar indicator shows that our test coverage has dropped.

    A screenshot of how the initital test is shown to have failed inside the App.test.js file
    (Large preview)

    These visual clues by Wallaby.js are convenient for test-driven development (TDD) since we get instant feedback on the state of our application regarding tests.

    Testing Our App Code

    Let’s modify src/App.test.js to check that the app renders correctly.

    Note: We will be using React Testing Library for our test which comes out of the box when you run create-react-app. See the docs for usage guide.

    We are going to need a couple of extra functions from @testing-library/react, update your @testing-library/react import to:

    import { render, fireEvent, cleanup } from '@testing-library/react';
    

    Then let’s replace the single test in src/App.js with:

    test('App renders correctly', () => { render(<App />); });
    

    Immediately you will see the indicator go green in both the src/App.test.js line where we test for the render of the app and also where we are calling render in our src/App.js.

    A screenshot of the App.test.js file opened in an editor showing green indicators
    (Large preview)

    Next, we will test that the initial value of the vote state is zero(0).

    it('Vote count starts at 0', () => {
      const { getByTitle } = render(<App />);
      const voteElement = getByTitle('vote count');
      expect(voteElement).toHaveTextContent(/^0$/);
    });
    

    Next, we will test if clicking the upvote 👍🏿 button increments the vote:

    it('Vote increments by 1 when upVote button is pressed', () => {
      const { getByTitle } = render(<App />);
      const upVoteButtonElement = getByTitle('upVote');
      const voteElement = getByTitle('vote count');
      fireEvent.click(upVoteButtonElement);
      expect(voteElement).toHaveTextContent(/^1$/);
    });
    

    We will also test for the downvote 👎🏿 interaction like so:

    it('Vote decrements by 1 when downVote button is pressed', () => {
      const { getByTitle } = render(<App />);
      const downVoteButtonElement = getByTitle('downVote');
      const voteElement = getByTitle('vote count');
      fireEvent.click(downVoteButtonElement);
      expect(voteElement).toHaveTextContent(/^-1$/);
    });
    

    Oops, this test is failing. Let’s work out why. Above the test, click the View story code lens link or the Debug Test link in the Wallaby.js output window and use the debugger to step through to the downVote function. We have a bug… we should have decremented the vote count by 1 but instead, we are decrementing by 2. Let’s fix our bug and decrement by 1.

    src/App.js
    function downVote() {
        setVote(vote - 1);
    }
    

    Watch now how Wallaby’s indicators go green and we know that all of our tests are passing:

    Our src/App.test.js should look like this:

    import React from 'react';
    import { render, fireEvent, cleanup } from '@testing-library/react';
    import App from './App';
    
    test('App renders correctly', () => {
      render(<App />);
    });
    
    it('Vote count starts at 0', () => {
      const { getByTitle } = render(<App />);
      const voteElement = getByTitle('vote count');
      expect(voteElement).toHaveTextContent(/^0$/);
    });
    
    it('Vote count increments by 1 when upVote button is pressed', () => {
      const { getByTitle } = render(<App />);
      const upVoteButtonElement = getByTitle('upVote');
      const voteElement = getByTitle('vote count');
      fireEvent.click(upVoteButtonElement);
      expect(voteElement).toHaveTextContent(/^1$/);
    });
    
    it('Vote count decrements by 1 when downVote button is pressed', () => {
      const { getByTitle } = render(<App />);
      const downVoteButtonElement = getByTitle('downVote');
      const voteElement = getByTitle('vote count');
      fireEvent.click(downVoteButtonElement);
      expect(voteElement).toHaveTextContent(/^-1$/);
    });
    
    afterEach(cleanup);
    

    After writing these tests, Wallaby.js shows us that the missing code paths that we initially identified before writing any tests have now been executed. We can also see that our coverage has increased. Again, you will notice how writing your tests with instant feedback from Wallaby.js allows you to see what’s going on with your tests right in your browser, which in turn improves your productivity.

    Final outcome of the Wallaby.js demo opened in a browser tab
    (Large preview)

    Conclusion

    From this article, you have seen how Wallaby.js improves your developer experience when testing JavaScript applications. We have investigated some key features of Wallaby.js, set it up in VS Code, and then tested a React application with Wallaby.js.

    Further Resources

    Smashing Editorial
    (ra, il)

    Source link

    web design

    Managing Long-Running Tasks In A React App With Web Workers — Smashing Magazine

    10/15/2020

    About The Author

    Awesome frontend developer who loves everything coding. I’m a lover of choral music and I’m working to make it more accessible to the world, one upload at a …
    More about
    Chidi

    In this tutorial, we’re going to learn how to use the Web Worker API to manage time-consuming and UI-blocking tasks in a JavaScript app by building a sample web app that leverages Web Workers. Finally, we’ll end the article by transferring everything to a React application.

    Response time is a big deal when it comes to web applications. Users demand instantaneous responses, no matter what your app may be doing. Whether it’s only displaying a person’s name or crunching numbers, web app users demand that your app responds to their command every single time. Sometimes that can be hard to achieve given the single-threaded nature of JavaScript. But in this article, we’ll learn how we can leverage the Web Worker API to deliver a better experience.

    In writing this article, I made the following assumptions:

    1. To be able to follow along, you should have at least some familiarity with JavaScript and the document API;
    2. You should also have a working knowledge of React so that you can successfully start a new React project using Create React App.

    If you need more insights into this topic, I’ve included a number of links in the “Further Resources” section to help you get up to speed.

    First, let’s get started with Web Workers.

    What Is A Web Worker?

    To understand Web Workers and the problem they’re meant to solve, it is necessary to get a grasp of how JavaScript code is executed at runtime. During runtime, JavaScript code is executed sequentially and in a turn-by-turn manner. Once a piece of code ends, then the next one in line starts running, and so on. In technical terms, we say that JavaScript is single-threaded. This behavior implies that once some piece of code starts running, every code that comes after must wait for that code to finish execution. Thus, every line of code “blocks” the execution of everything else that comes after it. It is therefore desirable that every piece of code finish as quickly as possible. If some piece of code takes too long to finish our program would appear to have stopped working. On the browser, this manifests as a frozen, unresponsive page. In some extreme cases, the tab will freeze altogether.

    Imagine driving on a single-lane. If any of the drivers ahead of you happen to stop moving for any reason, then, you have a traffic jam. With a program like Java, traffic could continue on other lanes. Thus Java is said to be multi-threaded. Web Workers are an attempt to bring multi-threaded behavior to JavaScript.

    The screenshot below shows that the Web Worker API is supported by many browsers, so you should feel confident in using it.

    Showing browser support chart for web workers
    Web Workers browser support. (Large preview)

    Web Workers run in background threads without interfering with the UI, and they communicate with the code that created them by way of event handlers.

    An excellent definition of a Web Worker comes from MDN:

    “A worker is an object created using a constructor (e.g. Worker() that runs a named JavaScript file — this file contains the code that will run in the worker thread; workers run in another global context that is different from the current window. Thus, using the window shortcut to get the current global scope (instead of self within a Worker will return an error.”

    A worker is created using the Worker constructor.

    const worker = new Worker('worker-file.js')

    It is possible to run most code inside a web worker, with some exceptions. For example, you can’t manipulate the DOM from inside a worker. There is no access to the document API.

    Workers and the thread that spawns them send messages to each other using the postMessage() method. Similarly, they respond to messages using the onmessage event handler. It’s important to get this difference. Sending messages is achieved using a method; receiving a message back requires an event handler. The message being received is contained in the data attribute of the event. We will see an example of this in the next section. But let me quickly mention that the sort of worker we’ve been discussing is called a “dedicated worker”. This means that the worker is only accessible to the script that called it. It is also possible to have a worker that is accessible from multiple scripts. These are called shared workers and are created using the SharedWorker constructor, as shown below.

    const sWorker = new SharedWorker('shared-worker-file.js')

    To learn more about Workers, please see this MDN article. The purpose of this article is to get you started with using Web workers. Let’s get to it by computing the nth Fibonacci number.

    Computing The Nth Fibonacci Number

    Note: For this and the next two sections, I’m using Live Server on VSCode to run the app. You can certainly use something else.

    This is the section you’ve been waiting for. We’ll finally write some code to see Web Workers in action. Well, not so fast. We wouldn’t appreciate the job a Web Worker does unless we run into the sort of problems it solves. In this section, we’re going to see an example problem, and in the following section, we’ll see how a web worker helps us do better.

    Imagine you were building a web app that allowed users to calculate the nth Fibonacci number. In case you’re new to the term ‘Fibonacci number’, you can read more about it here, but in summary, Fibonacci numbers are a sequence of numbers such that each number is the sum of the two preceding numbers.

    Mathematically, it is expressed as:

    Thus the first few numbers of the sequence are:

    1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ...

    In some sources, the sequence starts at F0 = 0, in which case the formula below holds for n > 1:

    In this article we’ll start at F1 = 1. One thing we can see right away from the formula is that the numbers follow a recursive pattern. The task at hand now is to write a recursive function to compute the nth Fibonacci number (FN).

    After a few tries, I believe you can easily come up with the function below.

    const fib = n => {
      if (n < 2) {
        return n // or 1
      } else {
        return fib(n - 1) + fib(n - 2)
      }
    }

    The function is simple. If n is less than 2, return n (or 1), otherwise, return the sum of the n-1 and n-2 FNs. With arrow functions and ternary operator, we can come up with a one-liner.

    const fib = n => (n < 2 ? n : fib(n-1) + fib(n-2))

    This function has a time complexity of 0(2n). This simply means that as the value of n increases, the time required to compute the sum increases exponentially. This makes for a really long-running task that could potentially interfere with our UI, for large values of n. Let’s see this in action.

    Note: This is by no means the best way to solve this particular problem. My choice of using this method is for the purpose of this article.

    To start, create a new folder and name it whatever you like. Now inside that folder create a src/ folder. Also, create an index.html file in the root folder. Inside the src/ folder, create a file named index.js.

    Open up index.html and add the following HTML code.

    <!DOCTYPE html>
    <html>
    <head>
      <link rel="stylesheet" href="styles.css">
    </head>
    <body>
      <div class="heading-container">
        <h1>Computing the nth Fibonnaci number</h1>
      </div>
      <div class="body-container">
        <p id='error' class="error"></p>
        <div class="input-div">
          <input id='number-input' class="number-input" type='number' placeholder="Enter a number" />
          <button id='submit-btn' class="btn-submit">Calculate</button>
        </div>
        <div id='results-container' class="results"></div>
      </div>
      <script src="http://www.smashingmagazine.com/src/index.js"></script>
    </body>
    </html>

    This part is very simple. First, we have a heading. Then we have a container with an input and a button. A user would enter a number then click on “Calculate”. We also have a container to hold the result of the calculation. Lastly, we include the src/index.js file in a script tag.

    You may delete the stylesheet link. But if you’re short on time, I have defined some CSS which you can use. Just create the styles.css file at the root folder and add the styles below:

    
    body {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      
      .body-container,
      .heading-container {
        padding: 0 20px;
      }
      
      .heading-container {
        padding: 20px;
        color: white;
        background: #7a84dd;
      }
      
      .heading-container > h1 {
        margin: 0;
      }
      
      .body-container {
        width: 50%
      }
      
      .input-div {
        margin-top: 15px;
        margin-bottom: 15px;
        display: flex;
        align-items: center;
      }
      
      .results {
        width: 50vw;
      }
      
      .results>p {
        font-size: 24px;
      }
      
      .result-div {
        padding: 5px 10px;
        border-radius: 5px;
        margin: 10px 0;
        background-color: #e09bb7;
      }
      
      .result-div p {
        margin: 5px;
      }
      
      span.bold {
        font-weight: bold;
      }
      
      input {
        font-size: 25px;
      }
      
      p.error {
        color: red;
      }
      
      .number-input {
        padding: 7.5px 10px;
      }
      
      .btn-submit {
        padding: 10px;
        border-radius: 5px;
        border: none;
        background: #07f;
        font-size: 24px;
        color: white;
        cursor: pointer;
        margin: 0 10px;
      }

    Now open up src/index.js let’s slowly develop it. Add the code below.

    const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));
    
    const ordinal_suffix = (num) => {
      // 1st, 2nd, 3rd, 4th, etc.
      const j = num % 10;
      const k = num % 100;
      switch (true) {
        case j === 1 && k !== 11:
          return num + "st";
        case j === 2 && k !== 12:
          return num + "nd";
        case j === 3 && k !== 13:
          return num + "rd";
        default:
          return num + "th";
      }
    };
    const textCont = (n, fibNum, time) => {
      const nth = ordinal_suffix(n);
      return `
      <p id='timer'>Time: <span class='bold'>${time} ms</span></p>
      <p><span class="bold" id='nth'>${nth}</span> fibonnaci number: <span class="bold" id='sum'>${fibNum}</span></p>
      `;
    };

    Here we have three functions. The first one is the function we saw earlier for calculating the nth FN. The second function is just a utility function to attach an appropriate suffix to an integer number. The third function takes some arguments and outputs a markup which we will later insert in the DOM. The first argument is the number whose FN is being computed. The second argument is the computed FN. The last argument is the time it takes to perform the computation.

    Still in src/index.js, add the below code just under the previous one.

    const errPar = document.getElementById("error");
    const btn = document.getElementById("submit-btn");
    const input = document.getElementById("number-input");
    const resultsContainer = document.getElementById("results-container");
    
    btn.addEventListener("click", (e) => {
      errPar.textContent = '';
      const num = window.Number(input.value);
    
      if (num < 2) {
        errPar.textContent = "Please enter a number greater than 2";
        return;
      }
    
      const startTime = new Date().getTime();
      const sum = fib(num);
      const time = new Date().getTime() - startTime;
    
      const resultDiv = document.createElement("div");
      resultDiv.innerHTML = textCont(num, sum, time);
      resultDiv.className = "result-div";
      resultsContainer.appendChild(resultDiv);
    });

    First, we use the document API to get hold of DOM nodes in our HTML file. We get a reference to the paragraph where we’ll display error messages; the input; the calculate button and the container where we’ll show our results.

    Next, we attach a “click” event handler to the button. When the button gets clicked, we take whatever is inside the input element and convert it to a number, if we get anything less than 2, we display an error message and return. If we get a number greater than 2, we continue. First, we record the current time. After that, we calculate the FN. When that finishes, we get a time difference that represents how long the computation took. In the remaining part of the code, we create a new div. We then set its inner HTML to be the output of the textCont() function we defined earlier. Finally, we add a class to it (for styling) and append it to the results container. The effect of this is that each computation will appear in a separate div below the previous one.

    Showing computed Fibonacci numbers up to 43
    Some Fibonacci numbers. (Large preview)

    We can see that as the number increases, the computation time also increases (exponentially). For instance, from 30 to 35, we had the computation time jump from 13ms to 130ms. We can still consider those operations to be “fast”. At 40 we see a computation time of over 1 second. On my machine, this is where I start noticing the page become unresponsive. At this point, I can no longer interact with the page while the computation is on-going. I can’t focus on the input or do anything else.

    Recall when we talked about JavaScript being single-threaded? Well, that thread has been “blocked” by this long-running computation, so everything else must “wait” for it to finish. It may start at a lower or higher value on your machine, but you’re bound to reach that point. Notice that it took almost 10s to compute that of 44. If there were other things to do on your web app, well, the user has to wait for Fib(44) to finish before they can continue. But if you deployed a web worker to handle that calculation, your users could carry on with something else while that runs.

    Let’s now see how web workers help us overcome this problem.

    An Example Web Worker In Action

    In this section, we’ll delegate the job of computing the nth FN to a web worker. This will help free up the main thread and keep our UI responsive while the computation is on-going.

    Getting started with web workers is surprisingly simple. Let’s see how. Create a new file src/fib-worker.js. and enter the following code.

    const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));
    
    onmessage = (e) => {
      const { num } = e.data;
      const startTime = new Date().getTime();
      const fibNum = fib(num);
      postMessage({
        fibNum,
        time: new Date().getTime() - startTime,
      });
    };

    Notice that we have moved the function that calculates the nth Fibonacci number, fib inside this file. This file will be run by our web worker.

    Recall in the section What is a web worker, we mentioned that web workers and their parent communicate using the onmessage event handler and postMessage() method. Here we’re using the onmessage event handler to listen to messages from the parent script. Once we get a message, we destructure the number from the data attribute of the event. Next, we get the current time and start the computation. Once the result is ready, we use the postMessage() method to post the results back to the parent script.

    Open up src/index.js let’s make some changes.

    ...
    
    const worker = new window.Worker("src/fib-worker.js");
    
    btn.addEventListener("click", (e) => {
      errPar.textContent = "";
      const num = window.Number(input.value);
      if (num < 2) {
        errPar.textContent = "Please enter a number greater than 2";
        return;
      }
    
      worker.postMessage({ num });
      worker.onerror = (err) => err;
      worker.onmessage = (e) => {
        const { time, fibNum } = e.data;
        const resultDiv = document.createElement("div");
        resultDiv.innerHTML = textCont(num, fibNum, time);
        resultDiv.className = "result-div";
        resultsContainer.appendChild(resultDiv);
      };
    });

    The first thing to do is to create the web worker using the Worker constructor. Then inside our button’s event listener, we send a number to the worker using worker.postMessage({ num }). After that, we set a function to listen for errors in the worker. Here we simply return the error. You can certainly do more if you want, like showing it in DOM. Next, we listen for messages from the worker. Once we get a message, we destructure time and fibNum, and continue the process of showing them in the DOM.

    Note that inside the web worker, the onmessage event is available in the worker’s scope, so we could have written it as self.onmessage and self.postMessage(). But in the parent script, we have to attach these to the worker itself.

    In the screenshot below you would see the web worker file in the sources tab of Chrome Dev Tools. What you should notice is that the UI stays responsive no matter what number you enter. This behavior is the magic of web workers.

    View of an active web worker file
    A running web worker file. (Large preview)

    We’ve made a lot of progress with our web app. But there’s something else we can do to make it better. Our current implementation uses a single worker to handle every computation. If a new message comes while one is running, the old one gets replaced. To get around this, we can create a new worker for each call to calculate the FN. Let’s see how to do that in the next section.

    Working With Multiple Web Workers

    Currently, we’re handling every request with a single worker. Thus an incoming request will replace a previous one that is yet to finish. What we want now is to make a small change to spawn a new web worker for every request. We will kill this worker once it’s done.

    Open up src/index.js and move the line that creates the web worker inside the button’s click event handler. Now the event handler should look like below.

    btn.addEventListener("click", (e) => {
      errPar.textContent = "";
      const num = window.Number(input.value);
      
      if (num < 2) {
        errPar.textContent = "Please enter a number greater than 2";
        return;
      }
      
      const worker = new window.Worker("src/fib-worker.js"); // this line has moved inside the event handler
      worker.postMessage({ num });
      worker.onerror = (err) => err;
      worker.onmessage = (e) => {
        const { time, fibNum } = e.data;
        const resultDiv = document.createElement("div");
        resultDiv.innerHTML = textCont(num, fibNum, time);
        resultDiv.className = "result-div";
        resultsContainer.appendChild(resultDiv);
        worker.terminate() // this line terminates the worker
      };
    });

    We made two changes.

    1. We moved this line const worker = new window.Worker("src/fib-worker.js") inside the button’s click event handler.
    2. We added this line worker.terminate() to discard the worker once we’re done with it.

    So for every click of the button, we create a new worker to handle the calculation. Thus we can keep changing the input, and each result will hit the screen once the computation finishes. In the screenshot below you can see that the values for 20 and 30 appear before that of 45. But I started 45 first. Once the function returns for 20 and 30, their results were posted, and the worker terminated. When everything finishes, we shouldn’t have any workers on the sources tab.

    showing Fibonacci numbers with terminated workers
    Illustration of Multiple independent workers. (Large preview)

    We could end this article right here, but if this were a react app, how would we bring web workers into it. That is the focus of the next section.

    Web Workers In React

    To get started, create a new react app using CRA. Copy the fib-worker.js file into the public/ folder of your react app. Putting the file here stems from the fact that React apps are single-page apps. That’s about the only thing that is specific to using the worker in a react application. Everything that follows from here is pure React.

    In src/ folder create a file helpers.js and export the ordinal_suffix() function from it.

    // src/helpers.js
    
    export const ordinal_suffix = (num) => {
      // 1st, 2nd, 3rd, 4th, etc.
      const j = num % 10;
      const k = num % 100;
      switch (true) {
        case j === 1 && k !== 11:
          return num + "st";
        case j === 2 && k !== 12:
          return num + "nd";
        case j === 3 && k !== 13:
          return num + "rd";
        default:
          return num + "th";
      }
    };

    Our app will require us to maintain some state, so create another file, src/reducer.js and paste in the state reducer.

    // src/reducers.js
    
    export const reducer = (state = {}, action) => {
      switch (action.type) {
        case "SET_ERROR":
          return { ...state, err: action.err };
        case "SET_NUMBER":
          return { ...state, num: action.num };
        case "SET_FIBO":
          return {
            ...state,
            computedFibs: [
              ...state.computedFibs,
              { id: action.id, nth: action.nth, loading: action.loading },
            ],
          };
        case "UPDATE_FIBO": {
          const curr = state.computedFibs.filter((c) => c.id === action.id)[0];
          const idx = state.computedFibs.indexOf(curr);
          curr.loading = false;
          curr.time = action.time;
          curr.fibNum = action.fibNum;
          state.computedFibs[idx] = curr;
          return { ...state };
        }
        default:
          return state;
      }
    };

    Let’s go over each action type one after the other.

    1. SET_ERROR: sets an error state when triggered.
    2. SET_NUMBER: sets the value in our input box to state.
    3. SET_FIBO: adds a new entry to the array of computed FNs.
    4. UPDATE_FIBO: here we look for a particular entry and replaces it with a new object which has the computed FN and the time taken to compute it.

    We shall use this reducer shortly. Before that, let’s create the component that will display the computed FNs. Create a new file src/Results.js and paste in the below code.

    // src/Results.js
    
    import React from "react";
    
    export const Results = (props) => {
      const { results } = props;
      return (
        <div id="results-container" className="results-container">
          {results.map((fb) => {
            const { id, nth, time, fibNum, loading } = fb;
            return (
              <div key={id} className="result-div">
                {loading ? (
                  <p>
                    Calculating the{" "}
                    <span className="bold" id="nth">
                      {nth}
                    </span>{" "}
                    Fibonacci number...
                  </p>
                ) : (
                  <>
                    <p id="timer">
                      Time: <span className="bold">{time} ms</span>
                    </p>
                    <p>
                      <span className="bold" id="nth">
                        {nth}
                      </span>{" "}
                      fibonnaci number:{" "}
                      <span className="bold" id="sum">
                        {fibNum}
                      </span>
                    </p>
                  </>
                )}
              </div>
            );
          })}
        </div>
      );
    };

    With this change, we start the process of converting our previous index.html file to jsx. This file has one responsibility: take an array of objects representing computed FNs and display them. The only difference from what we had before is the introduction of a loading state. So now when the computation is running, we show the loading state to let the user know that something is happening.

    Let’s put in the final pieces by updating the code inside src/App.js. The code is rather long, so we’ll do it in two steps. Let’s add the first block of code.

    import React from "react";
    import "./App.css";
    import { ordinal_suffix } from "./helpers";
    import { reducer } from './reducer'
    import { Results } from "./Results";
    function App() {
      const [info, dispatch] = React.useReducer(reducer, {
        err: "",
        num: "",
        computedFibs: [],
      });
      const runWorker = (num, id) => {
        dispatch({ type: "SET_ERROR", err: "" });
        const worker = new window.Worker('./fib-worker.js')
        worker.postMessage({ num });
        worker.onerror = (err) => err;
        worker.onmessage = (e) => {
          const { time, fibNum } = e.data;
          dispatch({
            type: "UPDATE_FIBO",
            id,
            time,
            fibNum,
          });
          worker.terminate();
        };
      };
      return (
        <div>
          <div className="heading-container">
            <h1>Computing the nth Fibonnaci number</h1>
          </div>
          <div className="body-container">
            <p id="error" className="error">
              {info.err}
            </p>
    
            // ... next block of code goes here ... //
    
            <Results results={info.computedFibs} />
          </div>
        </div>
      );
    }
    export default App;

    As usual, we bring in our imports. Then we instantiate a state and updater function with the useReducer hook. We then define a function, runWorker(), that takes a number and an ID and sets about calling a web worker to compute the FN for that number.

    Note that to create the worker, we pass a relative path to the worker constructor. At runtime, our React code gets attached to the public/index.html file, thus it can find the fib-worker.js file in the same directory. When the computation completes (triggered by worker.onmessage), the UPDATE_FIBO action gets dispatched, and the worker terminated afterward. What we have now is not much different from what we had previously.

    In the return block of this component, we render the same HTML we had before. We also pass the computed numbers array to the <Results /> component for rendering.

    Let’s add the final block of code inside the return statement.

            <div className="input-div">
              <input
                type="number"
                value={info.num}
                className="number-input"
                placeholder="Enter a number"
                onChange={(e) =>
                  dispatch({
                    type: "SET_NUMBER",
                    num: window.Number(e.target.value),
                  })
                }
              />
              <button
                id="submit-btn"
                className="btn-submit"
                onClick={() => {
                  if (info.num < 2) {
                    dispatch({
                      type: "SET_ERROR",
                      err: "Please enter a number greater than 2",
                    });
                    return;
                  }
                  const id = info.computedFibs.length;
                  dispatch({
                    type: "SET_FIBO",
                    id,
                    loading: true,
                    nth: ordinal_suffix(info.num),
                  });
                  runWorker(info.num, id);
                }}
              >
                Calculate
              </button>
            </div>

    We set an onChange handler on the input to update the info.num state variable. On the button, we define an onClick event handler. When the button gets clicked, we check if the number is greater than 2. Notice that before calling runWorker(), we first dispatch an action to add an entry to the array of computed FNs. It is this entry that will be updated once the worker finishes its job. In this way, every entry maintains its position in the list, unlike what we had before.

    Finally, copy the content of styles.css from before and replace the content of App.css.

    We now have everything in place. Now start up your react server and play around with some numbers. Take note of the loading state, which is a UX improvement. Also, note that the UI stays responsive even when you enter a number as high as 1000 and click “Calculate”.

    showing loading state while worker is active.
    Showing loading state and active web worker. (Large preview)

    Note the loading state and the active worker. Once the 46th value is computed the worker is killed and the loading state is replaced by the final result.

    Conclusion

    Phew! It has been a long ride, so let’s wrap it up. I encourage you to take a look at the MDN entry for web workers (see resources list below) to learn other ways of using web workers.

    In this article, we learned about what web workers are and the sort of problems they’re meant to solve. We also saw how to implement them using plain JavaScript. Finally, we saw how to implement web workers in a React application.

    I encourage you to take advantage of this great API to deliver a better experience for your users.

    Further Resources

    Smashing Editorial
    (ks, ra, yk, il)

    Source link

    web design

    React Form Validation With Formik And Yup — Smashing Magazine

    10/12/2020

    About The Author

    Nefe is a Frontend Developer who enjoys learning new things and sharing his knowledge with others.
    More about
    Nefe

    Forms are an integral part of how users interact with our websites and web applications. Validating the data the user passes through the form is a critical aspect of our jobs as web developers. However, it doesn’t have to be a pain-staking process. In this article, we’ll learn how Formik handles the state of the form data, validates the data, and handles form submission.

    As developers, it is our job to ensure that when users interact with the forms we set up, the data they send across is in the form we expect.

    In this article, we will learn how to handle form validation and track the state of forms without the aid of a form library.
    Next, we will see how the Formik library works. We’ll learn how it can be used incrementally with HTML input fields and custom validation rules. Then we will set up form validation using Yup and Formik’s custom components and understand how Yup works well with Formik in handling Form validation. We will implement these form validation methods to validate a simple sign up form I have set up.

    Note: This article requires a basic understanding of React.

    Form Validation In React

    On its own, React is powerful enough for us to be able to set up custom validation for our forms. Let’s see how to do that. We’ll start by creating our form component with initial state values. The following sandbox holds the code for our form:

    Form validation without the use of a library

    const Form = () => {
      const intialValues = { email: "", password: "" };
      const [formValues, setFormValues] = useState(intialValues);
      const [formErrors, setFormErrors] = useState({});
      const [isSubmitting, setIsSubmitting] = useState(false);
    }

    With the useState hook, we set state variables for the formValues, formErrors and isSubmitting.

    • The formValues variable holds the data the user puts into the input fields.
    • The formErrors variable holds the errors for each input field.
    • The isSubmitting variable is a boolean that tracks if the form is being submitted or not. This will be true only when there are no errors in the form.
    const submitForm = () => {
        console.log(formValues);
      };
    
     const handleChange = (e) => {
        const { name, value } = e.target;
        setFormValues({ ...formValues, [name]: value });
      };
    
    const handleSubmit = (e) => {
        e.preventDefault();
        setFormErrors(validate(formValues));
        setIsSubmitting(true);
      };
    
    const validate = (values) => {
        let errors = {};
        const regex = /^[^s@]+@[^s@]+.[^s@]{2,}$/i;
        if (!values.email) {
          errors.email = "Cannot be blank";
        } else if (!regex.test(values.email)) {
          errors.email = "Invalid email format";
        }
        if (!values.password) {
          errors.password = "Cannot be blank";
        } else if (values.password.length < 4) {
          errors.password = "Password must be more than 4 characters";
        }
        return errors;
      };
    
    useEffect(() => {
        if (Object.keys(formErrors).length === 0 && isSubmitting) {
          submitForm();
        }
      }, [formErrors]);

    Here, we have 4 form handlers and a useEffect set up to handle the functionality of our form.

    • handleChange
      This keeps the inputs in sync with the formValues state and updates the state as the user types.
    • validate
      We pass in the formValues object as a argument to this function, then based on the email and password meeting the validation tests, the errors object is populated and returned.
    • handleSubmit
      Whenever the form is submitted, the formErrors state variable is populated with whatever errors may exist using the setFormErrors(validate(formValues)) method.
    • useEffect
      Here, we check if the formErrors object is empty, and if isSubmitting is true. If this check holds true, then the submitForm() helper is called. It has single dependency, which is the formErrors object. This means it only runs when the formErrors object changes.
    • submitForm: this handles the submission of the form data.
    return (
        <div className="container">
          <h1>Sign in to continue</h1>
          {Object.keys(formErrors).length === 0 && isSubmitting && (
            <span className="success-msg">Signed in successfully</span>
          )}
          <form onSubmit={handleSubmit} noValidate>
            <div className="form-row">
              <label htmlFor="email">Email</label>
              <input
                type="email"
                name="email"
                id="email"
                value={formValues.email}
                onChange={handleChange}
                className={formErrors.email && "input-error"}
              />
              {formErrors.email && (
                <span className="error">{formErrors.email}</span>
              )}
            </div>
            <div className="form-row">
              <label htmlFor="password">Password</label>
              <input
                type="password"
                name="password"
                id="password"
                value={formValues.password}
                onChange={handleChange}
                className={formErrors.password && "input-error"}
              />
              {formErrors.password && (
                <span className="error">{formErrors.password}</span>
              )}
            </div>
            <button type="submit">Sign In</button>
          </form>
        </div>
      );

    Here, we pass in the handleChange helper functions to the inputs’ onChange attribute. We link the value of the inputs to the formValues object, making them controlled inputs. From the React docs, controlled inputs are inputs whose values are controlled by React. An input-error style is applied if there are any errors related to that specific input field. An error message is conditionally displayed beneath each input if there are any errors related to that specific input field. Finally, we check if there are any errors in the errors object and if isSubmitting is true. If these conditions hold true, then we display a message notifying the user that they signed in successfully.

    With this, we have a fully functional and validated form set up without the aid of a library. However, a form library like Formik with the aid of Yup can simplify the complexities of handling forms for us.

    What Are Formik And Yup?

    Right from the docs:

    “Formik is a small library that helps you with the 3 most annoying parts in handling forms:

    1. Getting values in and out of form state.
    2. Validation and error messages
    3. Handling form submission.

    Formik is a flexible library. It allows you to decide when and how much you want to use it. We can control how much functionality of the Formik library we use. It can be used with HTML input fields and custom validation rules, or Yup and the custom components it provides. Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React.

    Yup is a JavaScript object schema validator. While it has many powerful features, we’ll focus on how it helps us create custom validation rules so we don’t have to. This is a sample Yup object schema for a sign-up form. We’ll go into Yup and how it works in depth later in the article.

    const SignUpSchema = Yup.object().shape({
      firstName: Yup.string()
        .min(2, "Too Short!")
        .max(50, "Too Long!")
        .required("Firstname is required"),
    
      lastName: Yup.string()
        .min(2, "Too Short!")
        .max(50, "Too Long!")
        .required("Lastname is required"),
    
      phoneNumber: Yup.string()
        .required("Phone number is required")
        .matches(
    /^([0]{1}|+?[234]{3})([7-9]{1})([0|1]{1})([d]{1})([d]{7})$/g,
          "Invalid phone number"
        ),
    
      email: Yup.string().email().required("Email is required"),
    
      password: Yup.string()
        .required("Password is required")
        .min(6, "Password is too short - should be 6 chars minimum"),
    });

    Formik, HTML Input Fields And Custom Validation Rules

    The following sandbox holds the code for this form set up:

    The first thing we have to do is install Formik.

    npm i formik

    Then we can go ahead to import it in the file where we’ll make use of it.

    import { Formik } from "formik";

    Before creating the component, we need to create an initialValues and validate object which we’ll pass as props to the Formik component when we set it up. initialValues and validate are code snippets, not normal words.

    The decision to do this outside the component is not a technical one, but rather for readability of our code.

    const initialValues = {
      email: "",
      password: ""
    };

    initialValues: is an object that describes the initial values of the respective form fields. The name given to each key in the initialValues must correspond with the value of the name of the input field we want Formik to watch.

    const validate = (values) => {
      let errors = {};
      const regex = /^[^s@]+@[^s@]+.[^s@]{2,}$/i;
      if (!values.email) {
        errors.email = "Email is required";
      } else if (!regex.test(values.email)) {
        errors.email = "Invalid Email";
      }
      if (!values.password) {
        errors.password = "Password is required";
      } else if (values.password.length < 4) {
        errors.password = "Password too short";
      }
      return errors;
    };

    validate: this accepts a function that handles the form validation. The function accepts an object in the form of data values as an argument and validates each property in the object based on the rules defined. Each key in the values object must correspond with the name of the input field.

    const submitForm = (values) => {
      console.log(values);
    };

    onSubmit: This handles what happens after the user submits. The onSubmit prop takes a callback function that will only run when there are no errors, meaning the user inputs are valid.

    const SignInForm = () => {
      return (
        <Formik
          initialValues={initialValues}
          validate={validate}
          onSubmit={submitForm}
        >
          {(formik) => {
            const {
              values,
              handleChange,
              handleSubmit,
              errors,
              touched,
              handleBlur,
              isValid,
              dirty
            } = formik;
            return (
                <div className="container">
                  <h1>Sign in to continue</h1>
                  <form onSubmit={handleSubmit}>
                    <div className="form-row">
                      <label htmlFor="email">Email</label>
                      <input
                        type="email"
                        name="email"
                        id="email"
                        value={values.email}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        className={errors.email && touched.email ? 
                        "input-error" : null}
                      />
                      {errors.email && touched.email && (
                        <span className="error">{errors.email}</span>
                      )}
                    </div>
    
                    <div className="form-row">
                      <label htmlFor="password">Password</label>
                      <input
                        type="password"
                        name="password"
                        id="password"
                        value={values.password}
                        onChange={handleChange}
                        onBlur={handleBlur}
                        className={errors.password && touched.password ? 
                         "input-error" : null}
                      />
                      {errors.password && touched.password && (
                        <span className="error">{errors.password}</span>
                      )}
                    </div>
    
                    <button
                      type="submit"
                      className={dirty && isValid ? "" : "disabled-btn"}
                      disabled={!(dirty && isValid)}>
                      Sign In
                    </button>
                  </form>
                </div>
            );
          }}
        </Formik>
      );
    };

    We pass in the initialValues object, and the submitForm and validate functions we defined earlier into Formik’s initialValues, onSubmit and validate props respectively.

    Using the render props pattern, we have access to even more props the Formik API provides.

    1. values
      This holds the values of the user inputs.
    2. handleChange
      This is the input change event handler. It is passed to the input field <input onChange={handleChange}>. It handles the changes of the user inputs.
    3. handleSubmit
      The form submission handler. It is passed into the form <form onSubmit={props.handleSubmit}>. This fires the function passed into the onSubmit prop whenever the form is submitted.
    4. errors
      This object holds the validation errors that correspond to each input field, and is populated with the definitions we passed into the Yup object schema.
    5. touched
      This is an object that watches if a form field has been touched. Each key corresponds to the name of the input elements and has a boolean value.
    6. handleBlur
      This is the onBlur event handler, and it is passed to the input field <input onBlur={handleBlur} />. When the user removes focus from an input, this function is called. Without it, if there are any errors in the input when it loses focus, the errors will only display when the user tries to submit.
    7. isValid
      Returns true if there are no errors (i.e. the errors object is empty) and false otherwise.
    8. dirty
      This prop checks if our form has been touched or not. We can use this to disable our submit button when the form loads initially.

    When the form is submitted, Formik checks if there are any errors in the errors object. If there are, it aborts the submission and displays the errors. To display the span using HTML inputs, we conditionally render and style the error message of each respective input field if the field has been touched and there are errors for that field.

    <button
      type="submit"
      className={!(dirty && isValid) ? "disabled-btn" : ""}
      disabled={!(dirty && isValid)}>
          Sign In
    </button>

    Also, we can add a visual cue to the button. The button is conditionally styled and disable it if there are errors in the errors object using the isValid and the dirty props.

    Validation Using Formik’s Components And Yup

    This sandbox holds the final code for this setup.

    npm i yup
    import { Formik, Form, Field, ErrorMessage } from "formik";
    import * as Yup from "yup";

    We install Yup, import the Field, Form, and the ErrorMessage components from Formik.

    Formik makes form validation easy! When paired with Yup, they abstract all the complexities that surround handling forms in React. With that we can then go ahead to create the schema we’ll be using for the sign in form using Yup. Instead of creating custom validations for each possible input field, which can be tedious, depending on the number of fields there are, we can leave that to Yup to handle.

    const SignInSchema = Yup.object().shape({
      email: Yup.string().email().required("Email is required"),
    
      password: Yup.string()
        .required("Password is required")
        .min(4, "Password is too short - should be 4 chars minimum"),
    });

    Yup works similarly to how we define propTypes in React. We created an object schema with Yup’s object function. We define the shape of the validation object schema and pass it into Yup’s shape() method. The required() method. This method takes a string as an argument, and this string will be the error message. that displays whenever a required field is left blank.

    This schema has two properties:

    • An email property that is a string type and is required.
    • A password property that is of number type but is not required.

    We can chain validation is Yup as seen above. The properties of the schema object match the name of the input fields. The docs go into the different validation methods available in Yup.

    const SignInForm = () => {
      return (
        <Formik
          initialValues={initialValues}
          validationSchema={signInSchema}
          onSubmit={(values) => {
            console.log(values);
          }}
        >
          {(formik) => {
            const { errors, touched, isValid, dirty } = formik;
            return (
              <div className="container">
                <h1>Sign in to continue</h1>
                <Form>
                  <div className="form-row">
                    <label htmlFor="email">Email</label>
                    <Field
                      type="email"
                      name="email"
                      id="email"
                      className={errors.email && touched.email ? 
                      "input-error" : null}
                    />
                    <ErrorMessage name="email" component="span" className="error" />
                  </div>
    
                  <div className="form-row">
                    <label htmlFor="password">Password</label>
                    <Field
                      type="password"
                      name="password"
                      id="password"
                      className={errors.password && touched.password ? 
                      "input-error" : null}
                    />
                    <ErrorMessage
                      name="password"
                      component="span"
                      className="error"
                    />
                  </div>
    
                  <button
                    type="submit"
                    className={!(dirty && isValid) ? "disabled-btn" : ""}
                    disabled={!(dirty && isValid)}
                  >
                    Sign In
                  </button>
                </Form>
              </div>
            );
          }}
        </Formik>
      );
    };

    While using HTML input fields get the job done, Formik’s custom components make things even easier for us, and reduce the amount of code we have to write! What are these custom components Formik provides us?

    1. Formik
      We’ve been using this for a while now. This is required for the other components to be usable.
    2. Form
      A wrapper that wraps the HTML <form/> element. It automatically links the onSubmit method to the form’s submit event.
    3. Field
      In the background, this automatically links the form input’s onChange, onBlur and value attributes to Formik’s handleChange, handleBlur, and values object respectively. It uses the name prop to match up with the state and automatically keeps the state in sync with the input value. With this component, we can decide to display it as an input field we want using it’s as property. For example, will render a textarea. By default, it renders an HTML input field.
    4. ErrorMessage
      It handles rendering the error message for its respective field based on the value given to the name prop, which corresponds to the <Field />’s name prop. It displays the error message if the field has been visited and the error exists. By default, it renders a string is the component prop is not specified.

    We pass the signInSchema into Formik using the validationSchema prop. The Formik team loves the Yup validation library so they created a specific prop for Yup called validationSchema which transforms errors into objects and matches against their values and touched functions.

    Conclusion

    Users do not know or care how you handle form validation. However, for you the developer, it should be as painless a process as possible, and I believe Formik stands out as a solid choice in that regard.

    We have successfully looked at some of the options available to us when validating forms in React. We have seen how Formik can be used incrementally, and how it pairs well with Yup in handling form validation.

    Resources

    Smashing Editorial
    (ks, ra, yk, il)

    Source link