Introducing The FAR3 Stack: A Versatile Toolkit For Web Development.
Tags: react, redux, redis, javascript
Published: 2022-09-03 17:41:19

---
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ca4l77n4met8vf7rb99.PNG
---

So, you know the gist of the FARR stack, right? A couple of days ago, I introduced the FARR stack \[[0\]](#0). However, I think that the name looks redundant because of the two Rs at the front. Therefore,  we can add an additional R to that acronym to add [Redux](https://redux.js.org/) into the stack, which is a convenient tool to manage the global state of any web application. Hence the abbreviation becomes FARRR, or a more compact version, FAR3; the number 3 at the front represents the three Rs (React, Redux, Redis.), which means it is less MEAN and FAR more reaching!

Having sorted that out, you start to wonder: "what in the world is the FAR3 Stack?" And that's actually what we are trying to figure out through this article.

Note: This article assumes that you are already familiar with React and its ecosystem.

## 👉 Table Of Contents (TOC). <a name="TOC"></a>

* [The FAR3 Stack.](#the-far3-stack)
* [Benefits.](#benefits)
* [Downsides.](#downsides)
* [Getting Started.](#getting-started)
* [Project Requirements.](#project-requirements)
* [Implementation.](#implementation)
* [SignUp Component.](#signup-component)
* [SignIn Component.](#signin-component)
* [Configure The Routes.](#configure-the-routes)
* [Wrapping Up.](#wrapping-up)
* [References.](#references)

## The FAR3 Stack. <a name="the-far3-stack"></a>

##### 🔝 [Go To TOC](#TOC)

When it comes to web development, few can rival the FAR3 stack. To begin with, the FAR3 stack is a set of versatile tools that provide a comprehensive solution for web development and allows developers to create high-quality, responsive websites that are, most importantly, blazingly fast. Furthermore, it provides a great way to start with web development, as it is relatively easy to learn and use. Additionally, the FAR3 stack is flexible and can be used to create websites of any size or complexity; take [a recent project of mine](https://github.com/wiseaidev/chat) as a good example.

Now, in case you missed that [holy manifesto](https://dev.to/wiseai/the-farr-stack-manifesto-mcd), The FAR3 Stack is a new web development stack that includes four main components:

- Frontend: React, Redux.
- Backend: FastAPI.
- Database: Redis.

These components work well together to provide robust and blazingly fast web applications. The FAR3 Stack is an excellent choice for web development because all stack parts are open source and free to use. Additionally, it offers excellent performance and scalability.

While each technology has its own benefits, the stack as a whole provides several advantages for web developers.

## Benefits. <a name="benefits"></a>

##### 🔝 [Go To TOC](#TOC)

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/se3g7bp64z5l8cr3agqa.png">
  <figcaption>Stackoverflow, react tag \[[1\]](#1).</figcaption>
</figure>

There are many benefits to using the FAR3 Stack. Perhaps the most obvious one is that React and Redux are well-known JavaScript libraries frequently used in web development. This means that a large community of developers already familiar with React and Redux can provide support and assistance when needed. Moreover, React is a declarative, efficient, and flexible JavaScript library for building user interfaces. 

<figure>
  <img src="https://fastapi.tiangolo.com/img/async/parallel-burgers/parallel-burgers-01.png">
  <figcaption>FastAPI, parallel burgers \[[2\]](#2).</figcaption>
</figure>

Additionally, FastAPI is a modern, lightweight, and efficient web framework that makes it easy to build server-side applications. FastAPI also supports async, making it easy to take advantage of the benefits of asynchronous programming. This makes it ideal for powering server-side applications. 

<figure>
  <img src="https://redis.com/wp-content/uploads/2022/08/linear-scaling-redis-enterprise-1.svg?&auto=webp&quality=85,75&width=1200">
  <figcaption>Scaling shards and nodes \[[3\]](#3).</figcaption>
</figure>

Another advantage of using the FAR3 Stack is that it is relatively easy to scale. Redis, in particular, is designed for [horizontal scaling](https://redis.io/docs/manual/scaling/), which means it can easily handle increased traffic without significant performance degradation. This makes the FAR3 Stack a good choice for developing large-scale web applications. This can also help you keep costs down and ensure that your application can handle future growth. Additionally, Redis offers a rich set of features that make it easy to work with data. With Redis, you can easily store and query data, as well as index and aggregate data \[[4\]](#4). As a result, Redis is an ideal database for powering server-side applications built with the FAR3 stack.

And last but not least, the FAR3 Stack is an excellent choice for those who want complete control over their application. With the FAR3 Stack, you can customize every aspect of your application to meet your specific business needs. This gives you full control over your development environment.

In essence, the FAR3 stack is a great choice for web development because it offers a wide range of benefits. It's relatively easy to use, fast, and scalable and has a large community of users and developers. So if you're looking for a technology stack that can help you build great web applications, the FAR3 stack is an excellent option to consider.

## Downsides. <a name="downsides"></a>

##### 🔝 [Go To TOC](#TOC)

There are definitely some Downsides to using the FAR3 stack. One of the biggest is that it requires two programming languages, JavaScript for front-end and python for backend development. It can be a bit challenging to master both languages simultaneously. For example, developers do not need to learn multiple languages or frameworks to build a complete web application. In addition, it can be difficult to debug your application - especially if you're not well-versed in both languages. 

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/udle7nkqzrlmq6rc0ao4.png">
  <figcaption>A steep learning curve.</figcaption>
</figure>

Another one, the learning curve can be really steep – you'll need to be proficient in React, Fastapi, and Redis before you can even get started. But if you're just starting out, be aware that there may be a bit of a learning curve.

Yet, another downside is that this stack relies on React, which can be slow to load and render pages compared to other web development frameworks \[[5\]](#5). However, over recent years, React has become mature enough and blazingly fast by adapting new principles like lazy loading, automatic batching, and stuff of that nature \[[6\]](#6).

Finally, the FAR3 stack can be overkill for simple projects that don't require the whole gamut of features provided by React, FastAPI, and Redis. Other stacks might be a better fit if you're looking for a more lightweight solution.

That noted, whether you want to create a simple website or a complex enterprise application, the FAR3 Stack has the tools you need to get the job done. So now, let's see how we can use this stack in action.

In the following sections, and subsequent articles, we'll start by setting up our project using the create-react-app package and installing all the dependencies needed. Once that's done, we'll set up our front-end, back-end, and database. Finally, we'll put everything together and create our full-stack login/register app. With that noted, let's dive right in!

## Getting Started. <a name="getting-started"></a>

##### 🔝 [Go To TOC](#TOC)

Before starting, you must have `Node.js` installed on your machine. If you need help, check out the official docs to install [Node.js](https://nodejs.org/en/) and [npm](https://www.npmjs.com/). Once these prerequisites are installed, you can create a new project by initializing it using [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html).

If you've never used create-react-app before, it is a tool that will help us quickly set up a React project. To use it, simply type the following command into your terminal:

```
npx create-react-app login-register-app
```

This will create a new directory called `login-register-app`, which contains all of the files necessary for a React project. Once it's finished creating the project, change into the new directory and start the development server with the following commands:

```
cd login-register-app
npm start
```

Open a new tab in your browser, and you should see the default `create-react-app` page. 

## Project Requirements. <a name="project-requirements"></a>

##### 🔝 [Go To TOC](#TOC)

When setting up a new project, specific requirements must be met for the project to run smoothly. For example, our login/register system has the following workflow:

1. On the register page, the user must fill in personal information, such as first name, last name, email address, and password. Validation will be required for all fields. The form should also have a submit button.

2. When the form is submitted, a POST request should be made to the following endpoint: `http://localhost:8000/api/v1/auth/register`

3. If the request is successful, the user should be redirected to the login page. Otherwise, an error message should be displayed on the form.

4. On the login page, a user needs to provide an email and password for their newly created account to log in.

5. When the form is submitted, a Get request should be made to the following endpoint: `http://localhost:8000/api/v1/auth/login`

6. If the request is successful, the user should be redirected to the home page. The user can see all the website's features on the home page.

7. That's it! You now have a basic login/register form built with React!

Once you have understood these requirements, you can begin setting up your project.

## Implementation. <a name="implementation"></a>

##### 🔝 [Go To TOC](#TOC)

When it comes to start coding a React project, it's important to have a well-organized folder structure. This will make it easier to keep track of your files and code. To do so, create a new folder called "components" inside your "src" folder. This is where all of your React components will go.

Next, create another folder called "SignUp" in your src folder that represents our SignUp component. Within that folder, create a new file called index.js. This will be the main React component for our register form.

Having done that, remove all the unecessairy files and our project structure will look like the following:

```sh
.
├── package.json
├── package-lock.json
├── public
│    ├── favicon.ico
│    ├── index.html
│    ├── logo192.png
│    ├── logo512.png
│    ├── manifest.json
│    └── robots.txt
└── src
     ├── components
     │    └── SignUp
     │         └── index.js
     ├── App.js
     └── index.js
```

## SignUp Component. <a name="signup-component"></a>

##### 🔝 [Go To TOC](#TOC)

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ug0e5zk7frov0py2n2wu.png">
  <figcaption>SignUp page.</figcaption>
</figure>

After the project folder has been set up, the next step is to create a React component that will be used as the SignUp form. The SignUp form component should have four state objects: firstName, lastName, email, and password. We also need four additional state objects to handle error messages and render them on the UI: firstNameError, lastNameError, emailError, passwordError. These fields can be modeled by MUI components, meaning their values should be set by the value attribute of the <TextField> element or using a pure <input> html tag. But we will use MUI because we want something that looks elegant! 

Now, we need to install MUI. To do so, head to [the official MUI docs](https://mui.com/material-ui/getting-started/installation/) and follow the instructions. Essentially, all we need to do is to run the following command:


```sh
npm i @mui/material @mui/styled-engine-sc styled-components @mui/icons-material @emotion/react @emotion/styled
```

Having installed the MUI library on your machine, you can now create the following TextField component:

{% details <code>Show Code</code> %}

```javascript
import React, { useState } from "react";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import MailIcon from "@mui/icons-material/Mail";
import IconButton from "@mui/material/IconButton";

const SignUp = () => {

  const [email, setEmail] = useState("");
  const [emailError, setEmailError] = useState("");

  return (
    <div>
      <TextField
        fullWidth
        className="text-field-root"
        variant="outlined"
        label="Email Address"
        value={email}
        onChange={(e) => {
          setEmail(e.target.value);
          setEmailError("");
        }}
        helperText={emailError}
        InputProps={{
          startAdornment: (
            <InputAdornment position="start" variant="standard">
              <IconButton aria-label="Email" edge="end" disabled>
                <MailIcon />
              </IconButton>
            </InputAdornment>
          ),
        }}
      />
    </div>
  );
};

export default SignUp;
```

{% enddetails %}

Now we need to import this component into our `App.js` file:

{% details <code>Show Code</code> %}

```javascript
import SignUp from "./components/SignUp"

const App = () => {
  return (
    <div className="App">
      <SignUp />
    </div>
  );
}

export default App;
```

{% enddetails %}

Running `npm start` will result in the following component being rendered on a web browser tab:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ezije5fxvqwwxlrqz0h2.png">
  <figcaption>TextField Component.</figcaption>
</figure>

For more info about different props of the `TextField` component, you can refer to [the official documentation](https://mui.com/material-ui/react-text-field/#main-content).

Having that working nice and dandy, let's add the other three state variables described previously. Doing so will result in the following:

{% details <code>Show Code</code> %}

```javascript
import { useState } from "react";
import { Box } from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import PermIdentityIcon from "@mui/icons-material/PermIdentity";
import PersonIcon from "@mui/icons-material/Person";
import MailIcon from "@mui/icons-material/Mail";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from '@mui/icons-material/VisibilityOff';

const SignUp = () => {
  const [values, setValues] = useState({
    firstName: "",
    lastName: "",
    email: "",
    password: "",
    showPassword: false,
  });

  const [errorValues, setErrorValues] = useState({
    firstNameError: "",
    lastNameError: "",
    emailError: "",
    passwordError: "",
  });


  const handleChange = (prop) => (event) => { 
    setValues({ ...values, [prop]: event.target.value });
  };

  const handleErrorChange = (prop) => (event) => {
    if (typeof event == "string") setErrorValues({ ...errorValues, [prop]: event });
    else setErrorValues({ ...errorValues, [prop]: event.target.value });
  };

  const handleClickShowPassword = () => {
    setValues({
      ...values,
      showPassword: !values.showPassword,
    });
  };

  const handleMouseDownPassword = (event) => {
    event.preventDefault();
  };

  return (
    <Box
      component="form"
      sx={{ "& .MuiTextField-root": { m: 1, width: "25ch" } }}
      display="flex"
      flexDirection="column"
      alignItems="center"
      noValidate
      autoComplete="off"
      onClick={() => {}}
    >
      <Typography component="div" variant="h4" mb={3}>
        Create an account
      </Typography>
      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        width="40ch"
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="First Name"
          value={values.firstName}
          onChange={(e) => {
            handleChange("firstName")(e);
            handleErrorChange("firstNameError")("");
          }}
          helperText={errorValues.firstNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="First Name" edge="end" disabled>
                  <PermIdentityIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
        <TextField
          fullWidth
          variant="outlined"
          label="Last Name"
          value={values.lastName}
          onChange={(e) => {
            handleChange("lastName")(e);
            handleErrorChange("lastNameError")("");
          }}
          helperText={errorValues.lastNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Last Name" edge="end" disabled>
                  <PersonIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="Email Address"
          value={values.email}
          onChange={(e) => {
            handleChange("email")(e);
            handleErrorChange("emailError")("");
          }}
          helperText={errorValues.emailError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Email" edge="end" disabled>
                  <MailIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="Password"
          value={values.password}
          onChange={(e) => {
            handleChange("password")(e);
            handleErrorChange("passwordError")("");
          }}
          helperText={errorValues.passwordError}
          InputProps={{
            endAdornment:(
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  edge="end"
                >
                  {values.showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
          )
          }}
        />
      </Box>

      <Box
        display="flex"
        alignItems="center"
        justifyContent="space-between"
        mt={3}
        mb={3}
      >
        <Button variant="contained" color="primary">
          {"Sign Up"}
        </Button>
      </Box>
      <Box display="flex" alignItems="center" justifyContent="space-between">
        <Typography>Have an account? Sign In</Typography>
      </Box>
    </Box>
  );
};

export default SignUp;
```

{% enddetails %}

The last step is to wire up the register form component to submit the first_name, last_name, email, and password to the server when the form is submitted. This can be done by adding an onClick handler to the outermost `<Box>` component. In the `onClick` handler, you need to call a function that sends an HTTP request to the server with the form's data. For example, by adding the `onClick` handler, our code will result in the following listing:

{% details <code>Show Code</code> %}

```javascript
import { useState } from "react";
import { Box } from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import PermIdentityIcon from "@mui/icons-material/PermIdentity";
import PersonIcon from "@mui/icons-material/Person";
import MailIcon from "@mui/icons-material/Mail";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from '@mui/icons-material/VisibilityOff';

const SignUp = () => {
  const [values, setValues] = useState({
    firstName: "",
    lastName: "",
    email: "",
    password: "",
    showPassword: false,
  });

  const [errorValues, setErrorValues] = useState({
    firstNameError: "",
    lastNameError: "",
    emailError: "",
    passwordError: "",
  });


  const handleChange = (prop) => (event) => { 
    setValues({ ...values, [prop]: event.target.value });
  };

  const handleErrorChange = (prop) => (event) => {
    if (typeof event == "string") setErrorValues({ ...errorValues, [prop]: event });
    else setErrorValues({ ...errorValues, [prop]: event.target.value });
  };

  const handleClickShowPassword = () => {
    setValues({
      ...values,
      showPassword: !values.showPassword,
    });
  };

  const handleMouseDownPassword = (event) => {
    event.preventDefault();
  };

  const onClick = () => {
    if (!values.firstName) {
      handleErrorChange("firstNameError")("First Name is required!");
    } else if (!values.lastName) {
      handleErrorChange("lastNameError")("Last Name is required!");
    } else if (!values.email) {
      handleErrorChange("emailError")("Email is required!");
    } else if (!values.password) {
      handleErrorChange("passwordError")("Password is required!");
    } else {
      // await fetch('login-url')
      console.log("submit")
    }
  };

  return (
    <Box
      component="form"
      sx={{ "& .MuiTextField-root": { m: 1, width: "25ch" } }}
      display="flex"
      flexDirection="column"
      alignItems="center"
      noValidate
      autoComplete="off"
      onClick={onClick}
    >
      <Typography component="div" variant="h4" mb={3}>
        Create an account
      </Typography>
      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        width="40ch"
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="First Name"
          value={values.firstName}
          onChange={(e) => {
            handleChange("firstName")(e);
            handleErrorChange("firstNameError")("");
          }}
          helperText={errorValues.firstNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="First Name" edge="end" disabled>
                  <PermIdentityIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
        <TextField
          fullWidth
          variant="outlined"
          label="Last Name"
          value={values.lastName}
          onChange={(e) => {
            handleChange("lastName")(e);
            handleErrorChange("lastNameError")("");
          }}
          helperText={errorValues.lastNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Last Name" edge="end" disabled>
                  <PersonIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="Email Address"
          value={values.email}
          onChange={(e) => {
            handleChange("email")(e);
            handleErrorChange("emailError")("");
          }}
          helperText={errorValues.emailError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Email" edge="end" disabled>
                  <MailIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="Password"
          value={values.password}
          onChange={(e) => {
            handleChange("password")(e);
            handleErrorChange("passwordError")("");
          }}
          helperText={errorValues.passwordError}
          InputProps={{
            endAdornment:(
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  edge="end"
                >
                  {values.showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
          )
          }}
        />
      </Box>

      <Box
        display="flex"
        alignItems="center"
        justifyContent="space-between"
        mt={3}
        mb={3}
      >
        <Button variant="contained" color="primary">
          {"Sign Up"}
        </Button>
      </Box>
      <Box display="flex" alignItems="center" justifyContent="space-between">
        <Typography>Have an account? Sign In</Typography>
      </Box>
    </Box>
  );
};

export default SignUp;
```

{% enddetails %}

However, in order to adhere to [the separation of concern](https://en.wikipedia.org/wiki/Separation_of_concerns) principle, we should move API calls into a different file. Doing so will make our code more organized and easier to maintain in the long run.

Now, we will create a new file called `api.js` at the top-level directory along with our `App` component, and move the `fetch/axios` call into that file.

{% details <code>Show Code</code> %}

```javascript
import {
  fetchError,
  fetchStart,
  fetchSuccess,
} from "../redux/commonReducer/actions";
import axios from "axios";

const axiosJson = () => {
  return axios.create({
    baseURL: `http://localhost:8000/api/v1/`,
    headers: { "Content-Type": "application/json" },
  });
};

const axiosJsonObject = axiosJson()

export const onRegister = (formData) => {
    return (dispatch) => {
      dispatch(fetchStart());
      axiosJsonObject
        .post(
          "auth/register",
          JSON.stringify({
            first_name: formData.firstName,
            last_name: formData.lastName,
            email: formData.email,
            password: formData.password,
          })
        )
        .then(({ data }) => {
          if (data.status_code === 201) {
            localStorage.setItem("token", data.token.value);
            axiosJsonObject.defaults.headers.common["Authorization"] =
              "Bearer " + data.token.value;
            dispatch(fetchSuccess(data.message));
          } else {
            dispatch(fetchError(data.message));
          }
        })
        .catch(function (error) {
          dispatch(fetchError(""));
        });
    };
}
```

{% enddetails %}

Now that we have created and configured our axios object and methods in the `Axios.js` file, we can import it into our `SignUp` form component and use it from the `onClick` handler. The `SignUp` component should now look like this:

{% details <code>Show Code</code> %}

```javascript
import { useState } from "react";
import { Box } from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import PermIdentityIcon from "@mui/icons-material/PermIdentity";
import PersonIcon from "@mui/icons-material/Person";
import MailIcon from "@mui/icons-material/Mail";
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { useDispatch } from "react-redux";
import { onRegister } from "../../api/Axios";

const SignUp = () => {
  const [values, setValues] = useState({
    firstName: "",
    lastName: "",
    email: "",
    password: "",
    showPassword: false,
  });

  const [errorValues, setErrorValues] = useState({
    firstNameError: "",
    lastNameError: "",
    emailError: "",
    passwordError: "",
  });

  const dispatch = useDispatch();

  const handleChange = (prop) => (event) => { 
    setValues({ ...values, [prop]: event.target.value });
  };

  const handleErrorChange = (prop) => (event) => {
    if (typeof event == "string") setErrorValues({ ...errorValues, [prop]: event });
    else setErrorValues({ ...errorValues, [prop]: event.target.value });
  };

  const handleClickShowPassword = () => {
    setValues({
      ...values,
      showPassword: !values.showPassword,
    });
  };

  const handleMouseDownPassword = (event) => {
    event.preventDefault();
  };

  const onClick = () => {
    if (!values.firstName) {
      handleErrorChange("firstNameError")("First Name is required!");
    } else if (!values.lastName) {
      handleErrorChange("lastNameError")("Last Name is required!");
    } else if (!values.email) {
      handleErrorChange("emailError")("Email is required!");
    } else if (!values.password) {
      handleErrorChange("passwordError")("Password is required!");
    } else {
      dispatch(onRegister(values))
    }
  };
  return (
    <Box
      component="form"
      sx={{ "& .MuiTextField-root": { m: 1, width: "25ch" } }}
      display="flex"
      flexDirection="column"
      alignItems="center"
      noValidate
      autoComplete="off"
      onClick={onClick}
    >
      <Typography component="div" variant="h4" mb={3}>
        Create an account
      </Typography>
      <Box
        display="flex"
        flexDirection={{ xs: "column", md: "row" }}
        width="40ch"
        alignItems="center"
      >
        <TextField
          fullWidth
          variant="outlined"
          label="First Name"
          value={values.firstName}
          onChange={(e) => {
            handleChange("firstName")(e);
            handleErrorChange("firstNameError")("");
          }}
          helperText={errorValues.firstNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="First Name" edge="end" disabled>
                  <PermIdentityIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
        <TextField
          fullWidth
          variant="outlined"
          label="Last Name"
          value={values.lastName}
          onChange={(e) => {
            handleChange("lastName")(e);
            handleErrorChange("lastNameError")("");
          }}
          helperText={errorValues.lastNameError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Last Name" edge="end" disabled>
                  <PersonIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box>
        <TextField
          fullWidth
          variant="outlined"
          label="Email Address"
          value={values.email}
          onChange={(e) => {
            handleChange("email")(e);
            handleErrorChange("emailError")("");
          }}
          helperText={errorValues.emailError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Email" edge="end" disabled>
                  <MailIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box>
        <TextField
          fullWidth
          variant="outlined"
          label="Password"
          value={values.password}
          onChange={(e) => {
            handleChange("password")(e);
            handleErrorChange("passwordError")("");
          }}
          helperText={errorValues.passwordError}
          InputProps={{
            endAdornment:(
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  edge="end"
                >
                  {values.showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
          )
          }}
        />
      </Box>

      <Box
        display="flex"
        alignItems="left"
        justifyContent="space-between"
        mt={3}
        mb={3}
      >
        <Button variant="contained" color="primary">
          {"Sign Up"}
        </Button>
      </Box>
      <Box display="flex" alignItems="left" justifyContent="space-between">
        <Typography>Have an account? Sign In</Typography>
      </Box>
    </Box>
  );
};

export default SignUp;
```

{% enddetails %}


<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlbolj3w8u7tj3c5rf07.gif" /><figcaption>Redux Application Data Flow [<a href="#7">7</a>].</figcaption>
</figure>


Notice the use of a dispatch function and a reducer in the follow-up section, which are objects used by redux to manage various states of our app, which can be helpful for debugging purposes. Essentially, a dispatcher is a mechanism by which actions can communicate with the store. When an action is dispatched, like `fetchStart`, the dispatcher will call upon the reducer function to update the application's state accordingly. Now, it is time to create the reducer mentioned above, in this case, `commonReducer`. To do so, let's create a redux folder structured in the following manner:

```sh
└── redux
    ├── commonReducer
    │    ├── actions
    │    │    └── index.js
    │    ├── index.js
    │    └── selectors
    │         └── index.js
    ├── rootReducer.js
    └── store.js
```

In the `redux/commonReducer/actions/index.js` file, we will define all of the actions that we will be able to dispatch. For our example, we have used three actions:

{% details <code>Show Code</code> %}

```javascript
import {
  FETCH_ERROR,
  FETCH_START,
  FETCH_SUCCESS,
} from "../../../constants/ActionTypes";

export const fetchSuccess = (message) => {
  return (dispatch) => {
    dispatch({
      type: FETCH_SUCCESS,
      payload: message || "",
    });
  };
};
export const fetchError = (error) => {
  return (dispatch) => {
    dispatch({
      type: FETCH_ERROR,
      payload: error,
    });
  };
};

export const fetchStart = () => {
  return (dispatch) => {
    dispatch({
      type: FETCH_START,
    });
  };
};
```

{% enddetails %}

Now, we need to create our reducer `redux/commonReducer/index.js`. A reducer is a mapping of how different actions should affect the application's state. In this case, we have two states, message, and errorMessage. This will result in the following reducer:

{% details <code>Show Code</code> %}

```javascript
import {
  FETCH_ERROR,
  FETCH_START,
  FETCH_SUCCESS,
} from "../../constants/ActionTypes";

const initialState = {
  error: "",
  message: "",
  loading: false,
};

const commonReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_START: { 
      // we use the spread operator `...` to make sure we don't mutate the state directly. We want to return a new state object every time an action is dispatched. 
      return { ...state, error: "", message: "", loading: true };
    }
    case FETCH_SUCCESS: {
      return { ...state, error: "", loading: false, message: action.payload };
    }
    case FETCH_ERROR: {
      return { ...state, loading: false, message: "", error: action.payload };
    }
    default:
      // don't forget to add the default case which will just return the state if no actions were matched
      return state;
  }
};

export default commonReducer;
```

{% enddetails %}

By utilizing both a dispatcher and a reducer, we can have much more granular control over our application state management. Again, as mentioned previously, this can be extremely helpful for debugging purposes and maintaining a consistent state across different parts of the application.

The `commonReducer/selectors/index.js` file is a selector used when we need to select/access the value of a state variable within a component of our app, hence the name selector. As you can tell, we have three selectors:

```javascript
export const message = (state) => state.common.message;
export const loading = (state) => state.common.loading;
export const error = (state) => state.common.error;
```

Each time we want to access the value of a given app state, say `message`, we can use `useSelector` from the `redux` module, like the following:

{% details <code>Show Code</code> %}

```javascript
import { useSelector } from 'react-redux';
import {message} from "../../redux/commonReducer/selectors"

const Message = () => {
  const message = useSelector(message);

  return (
    <div>{message}</div>
  );
};

export default Message;
```

{% enddetails %}

Note that we have created a reducer that will help us to know whether or not an API call was successful. For instance, let's go back to our `onRegister` API method: 

{% details <code>Show Code</code> %}

```javascript

export const onRegister = (formData) => {
    return (dispatch) => {
      dispatch(fetchStart());
      axiosJsonObject
        .post(
          "auth/register",
          JSON.stringify({
            first_name: formData.firstName,
            last_name: formData.lastName,
            email: formData.email,
            password: formData.password,
          })
        )
        .then(({ data }) => {
          if (data.status_code === 201) {
            localStorage.setItem("token", data.token.value);
            axiosJsonObject.defaults.headers.common["Authorization"] =
              "Bearer " + data.token.value;
            dispatch(fetchSuccess(data.message));
          } else {
            dispatch(fetchError(data.message));
          }
        })
        .catch(function (error) {
          dispatch(fetchError(""));
        });
    };
}
```

{% enddetails %}

- `fetchStart` will tell us that this API method has been called.
- `fetchSuccess` will set the value of the message on a successful API call.
- `fetchError` will set the value of the message if an exception is being thrown during the API call or on a failed attempt.

This way, when debugging the app, we can tell what went wrong during the API call.

With that noted, it is time to configure our redux store. Now, create a file named `rootReducer` that is configured in the following manner:

{% details <code>Show Code</code> %}

```javascript
import { combineReducers } from "redux";
import commonReducer from "./commonReducer";

const rootReducer = combineReducers({
  common: commonReducer,
});

export default rootReducer;
```

{% enddetails %}

Note the key `common` that is used to access a state in our selector: 

```javascript
export const message = (state) => state.common.message;
```

And finally, the `redux/store.js` is configured in the following way:

{% details <code>Show Code</code> %}

```javascript
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./rootReducer";

const bindMiddleware = (middleware) => {
  if (process.env.NODE_ENV !== "production") {
    const { composeWithDevTools } = require("redux-devtools-extension");
    return composeWithDevTools(applyMiddleware(...middleware));
  }
  return applyMiddleware(...middleware);
};
const store = createStore(rootReducer, bindMiddleware([thunk]));

export default store;
```

{% enddetails %}

Now, we need to wrap our app with a redux provider to make redux work in our app:

{% details <code>Show Code</code> %}

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Provider store={store}>
        <App />
      </Provider>
    </BrowserRouter>
  </React.StrictMode>
);
```

{% enddetails %}

We then need to install the missing dependencies:

```sh
npm i axios redux-devtools-extension react-router-dom redux-thunk react-redux
```

After installing these libraries, we can run our app:

```sh
npm start
```

If you've been paying attention, you may have noticed that we have installed the `redux-devtools-extension` package. This powerful tool allows us to debug our React app more efficiently.

If you're not familiar with the extension, I highly recommend taking some time to check it out. It's a great way to understand better how Redux works and can save you a lot of time when debugging your apps.

With the [`redux-devtools-extension`](https://github.com/reduxjs/redux-devtools), we can now easily see all the actions that have been dispatched, as well as the current state of our store. This extension also allows us to time travel, meaning we can go back and forth through different states to see how our application got to where it is, as shown in the image below.

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjafl4c0nir65ch8uhrx.png">
  <figcaption><a href="https://github.com/reduxjs/redux-devtools" target="_blank">Redux devtools extension</a>.</figcaption>
</figure>

Now that we have created the signup component, it is time to create the signin component. This will allow us to complete the authentication process for our users. The signin component will take care of verifying the user's credentials and, if they are correct, authenticating the user. Once the user is authenticated, they can access the authorized areas of our website.

As before, we'll use react and redux to implement this functionality. Once again, the goal is to create a simple, elegant component that allows users to sign in quickly and easily. With that in mind, let's get started!

## SignIn Component. <a name="signin-component"></a>

##### 🔝 [Go To TOC](#TOC)

The first thing we need to do is create a react component for our sign-in component. As with the signup component, we'll need to store the email and password as state objects. In addition,  we'll need to create a signin action in our redux store that dispatches the API calls to submit user credentials to the server. Finally, you'll need to create an Auth reducer that handles the response from the server and sets auth states accordingly.

Let's start by creating a `SignIn` component under the `components` directory. To do so, create a new directory called `SignIn` and within that directory, create a file called `index.js` which will contain the following logic for our SignIn component:

{% details <code>Show Code</code> %}

```javascript
import { useState } from "react";
import { Box } from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import IconButton from "@mui/material/IconButton";
import InputAdornment from "@mui/material/InputAdornment";
import MailIcon from "@mui/icons-material/Mail";
import LockIcon from '@mui/icons-material/Lock';
import Visibility from "@mui/icons-material/Visibility";
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { useDispatch } from "react-redux";
import { onSignIn } from "../../api/Axios";

const SignIn = () => {
  const [values, setValues] = useState({
    email: "",
    password: "",
    showPassword: false,
  });

  const [errorValues, setErrorValues] = useState({
    emailError: "",
    passwordError: "",
  });

  const dispatch = useDispatch();

  const handleChange = (prop) => (event) => { 
    setValues({ ...values, [prop]: event.target.value });
  };

  const handleErrorChange = (prop) => (event) => {
    if (typeof event == "string") setErrorValues({ ...errorValues, [prop]: event });
    else setErrorValues({ ...errorValues, [prop]: event.target.value });
  };

  const handleClickShowPassword = () => {
    setValues({
      ...values,
      showPassword: !values.showPassword,
    });
  };

  const handleMouseDownPassword = (event) => {
    event.preventDefault();
  };

  const onClick = () => {
    if (!values.email) {
      handleErrorChange("emailError")("Email is required!");
    } else if (!values.password) {
      handleErrorChange("passwordError")("Password is required!");
    } else {
      dispatch(onSignIn(values))
    }
  };
  return (
    <Box
      component="form"
      sx={{ "& .MuiTextField-root": { m: 1, width: "25ch" } }}
      display="flex"
      flexDirection="column"
      alignItems="center"
      noValidate
      autoComplete="off"
      onClick={onClick}
    >
      <Typography component="div" variant="h5" mb={3}>
        Login to your account
      </Typography>

      <Box>
        <TextField
          fullWidth
          variant="outlined"
          label="Email Address"
          value={values.email}
          onChange={(e) => {
            handleChange("email")(e);
            handleErrorChange("emailError")("");
          }}
          helperText={errorValues.emailError}
          InputProps={{
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Email" edge="end" disabled>
                  <MailIcon />
                </IconButton>
              </InputAdornment>
            ),
          }}
        />
      </Box>

      <Box>
        <TextField
          fullWidth
          type={values.showPassword ? 'text' : 'password'}
          variant="outlined"
          label="Password"
          value={values.password}
          onChange={(e) => {
            handleChange("password")(e);
            handleErrorChange("passwordError")("");
          }}
          helperText={errorValues.passwordError}
          InputProps={{
            endAdornment:(
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  onClick={handleClickShowPassword}
                  onMouseDown={handleMouseDownPassword}
                  edge="end"
                >
                  {values.showPassword ? <VisibilityOff /> : <Visibility />}
                </IconButton>
              </InputAdornment>
          ),
            startAdornment: (
              <InputAdornment position="start" variant="standard">
                <IconButton aria-label="Email" edge="end" disabled>
                  <LockIcon />
                </IconButton>
              </InputAdornment>
            )
          }}
        />
      </Box>

      <Box
        display="flex"
        alignItems="left"
        justifyContent="space-between"
        mt={3}
        mb={3}
      >
        <Button variant="contained" color="primary">
          {"Sign Up"}
        </Button>
      </Box>
      <Box display="flex" alignItems="left" justifyContent="space-between">
        <Typography>Don't have an account? Sign Up</Typography>
      </Box>
    </Box>
  );
};

export default SignIn;
```

{% enddetails %}

Similarly to the signup component, we need to create an API call to submit user credentials to the server. However, we also need to create an auth reducer to dispatch actions to the store and set the authUser state. This authUser state will be an object that represents whether or not a user is logged in. If they are logged in, we'll set its value to a user object returned from the server, and if they're not, we'll set it to null.

When a user signs in to our app, they need to provide some credentials, which the API then sends off to the server to verify. Once the server has verified the user's credentials, it will send back a token that can be stored in local storage and used to authenticate future requests.

The authentication reducer will be responsible for dispatching actions to store and setting the authUser state. This state will determine whether or not a user is signed in. The authentication reducer will also be responsible for handling any errors that occur during sign-in.

Now, it is time to create the reducer described above; in this case, let's call it `authReducer`. To do so, let's create a redux folder structured in the following manner:

```sh
src
 └── redux
     └── authReducer
          ├── actions
          │    └── index.js
          ├── index.js
          └── selectors
               └── index.js
```

In the `actions/index.js`, we need to have the following logic:

{% details <code>Show Code</code> %}

```javascript
import {
  UPDATE_AUTH_USER,
} from "../../../constants/ActionTypes";

export const setAuthUser = (user) => {
  return (dispatch) => {
    dispatch({
      type: UPDATE_AUTH_USER,
      payload: user,
    });
  };
};
```

{% enddetails %}

The reducer should look like the following:

{% details <code>Show Code</code> %}

```javascript
import {
  UPDATE_AUTH_USER
} from "../../constants/ActionTypes";

const initialState = {
  authUser: "",
};

const authReducer = (state = initialState, action) => {
  switch (action.type) {
    case UPDATE_AUTH_USER: {
      return {
        ...state,
        authUser: action.payload,
        loadUser: true,
      };
    }
    default:
      return state;
  }
};

export default authReducer;
```

{% enddetails %}

And finally, we have our auth selecter:

```javascript
export const authUser = (state) => state.auth.authUser;
```

Now, we need to combine the auth reducer with other reducers, which is the commonReducer:

{% details <code>Show Code</code> %}

```javascript
import { combineReducers } from "redux";
import authReducer from "./authReducer";
import commonReducer from "./commonReducer";

const rootReducer = combineReducers({
  common: commonReducer,
  auth: authReducer,
});

export default rootReducer;
```

{% enddetails %}

And that's pretty much it. We have fully configured our new auth reducer in redux.

Next, we need to create our `onSignIn` API endpoint to submit user credentials to the server. Doing so, we will end up with the following logic:

{% details <code>Show Code</code> %}

```javascript
export const onSignIn = (formData) => {
    return (dispatch) => {
      dispatch(fetchStart());
      axiosJsonObject
        .post(
          "auth/register",
          JSON.stringify({
            first_name: formData.firstName,
            last_name: formData.lastName,
            email: formData.email,
            password: formData.password,
          })
        )
        .then(({ data }) => {
          if (data.status_code === 201) {
            localStorage.setItem("token", data.token.value);
            axiosJsonObject.defaults.headers.common["Authorization"] =
              "Bearer " + data.token.value;
            dispatch(fetchSuccess(data.message));
          } else {
            dispatch(fetchError(data.message));
          }
        })
        .catch(function (error) {
          dispatch(fetchError(""));
        });
    };
}
```

{% enddetails %}

Notice the line:

```javascript
localStorage.setItem("token", data.token.value);
```

This sets the token in local storage with a key "token" and the token value returned from the server.

The last step is to wire up the auth reducer and the API calls to set the authUser state for a given token. This can be done by dispatching the redux `setAuthUser` action.

{% details <code>Show Code</code> %}

```javascript
export const getAuthUser = (loaded = false, token, message) => {
  return (dispatch) => {
    if (!token) {
      const token = localStorage.getItem("token");
      axiosJson.defaults.headers.common["Authorization"] = "Bearer " + token;
    }
    dispatch(fetchStart());
    dispatch(updateLoadUser(loaded));
    axJson
      .get("user/profile")
      .then(({ data }) => {
        if (data.status_code === 200) {
          dispatch(fetchSuccess(message));
          dispatch(updateLoadUser(true));
          dispatch(setAuthUser(data.user));
          dispatch(setCurrentUser(data.user));
          // store the user in localStorage
          localStorage.setItem("user", JSON.stringify(data.user));
        } else {
          dispatch(updateLoadUser(true));
        }
      })
      .catch(function (error) {
        dispatch("");
      });
  };
}
```

{% enddetails %}

Now, we can call this method in `onSignIn` to automatically set a given user's value. Our `onSignIn` method becomes:

{% details <code>Show Code</code> %}

```javascript
export const onSignIn = (formData) => {
    return (dispatch) => {
      dispatch(fetchStart());
      axiosJsonObject
        .get(
          "auth/login",
          JSON.stringify({
            email: formData.email,
            password: formData.password,
          })
        )
        .then(({ data }) => {
          if (data.status_code === 201) {
            localStorage.setItem("token", data.token.value);
            axiosJsonObject.defaults.headers.common["Authorization"] =
              "Bearer " + data.token.value;
            dispatch(getAuthUser(data.message));            
          } else {
            dispatch(fetchError(data.message));
          }
        })
        .catch(function (error) {
          dispatch(fetchError(""));
        });
    };
}
```

{% enddetails %}

Now, you can navigate to `localhost:3000` to see the SignIn component in action:

<figure>
  <img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35karkxryr27l7uumqu3.png">
  <figcaption>SignIn Component.
  </figcaption>
</figure>

## Configure the Routes. <a name="configure-the-routes"></a>

##### 🔝 [Go To TOC](#TOC)

<figure>
  <img src="https://images.unsplash.com/photo-1587317027973-cb15388ccfaf?ixlib=rb-1.2.1&dl=matt-seymour-VsVTeEgaHNQ-unsplash.jpg&q=80&fm=jpg&crop=entropy&cs=tinysrgb">
  <figcaption>Photo by <a href="https://unsplash.com/@mattseymour?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Matt Seymour</a> on <a href="https://unsplash.com/s/photos/routes?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
  </figcaption>
</figure>

Now that we have implemented login and register components, it is time to configure the app routes. This will ensure that users can navigate to the appropriate pages when they log in or register for your app. By properly configuring the app routes, we can provide a seamless user experience for our app's users.

We can use [React Router library V6](https://reactrouter.com/en/main) to help us with this. It makes it easy to define and manage routes in a React application.

First, we need to install React Router if it is not already installed:

```sh
npm i react-router-dom@6
```

Then, we can import it into our App.js file:

```javascript
import { useLocation, Routes, Route, Navigate } from "react-router-dom";
```

Next, we need to define our routes. For our login and register components, we will create a `/signin` and `/signup` routes, respectively:

{% details <code>Show Code</code> %}

```javascript
import SignUp from "./components/SignUp";
import SignIn from "./components/SignIn";
import React, { useEffect, useState } from "react";
import { useLocation, Routes, Route, Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { authUser } from "./redux/authReducer/selectors";

const App = () => {
  const [currentAuthUser, setCurrentAuthUser] = useState(useSelector(authUser));
  const location = useLocation();
  const dispatch = useDispatch();

  useEffect(() => {
    if (localStorage.getItem("user")) {
      setCurrentAuthUser(JSON.parse(localStorage.getItem("user")));
    }
    // eslint-disable-next-line
  }, [dispatch, localStorage.getItem("user")]);

  if (currentAuthUser && location.pathname === "/sigin") {
    return (
      <>
        <Navigate to={"/home"} replace />
      </>
    );
  }

  return (
    <>
        <Routes>
          <Route
            exact
            path="/"
            element={<Navigate replace to="/signin" />}
          ></Route>
          <Route
            path="/home"
            element={
              <div className="home">
                {currentAuthUser ? (
                  <div> Welcome back {currentAuthUser.name}!</div>
                ) : (
                  <Navigate to={"/signin"} replace />
                )}
              </div>
            }
          ></Route>
          <Route
            path="/signup"
            element={
              <div className="signup">
                {<SignUp />}
              </div>
            }
          ></Route>
          <Route
            path="/signin"
            element={
              <div className="Signin">
                {<SignIn />}
              </div>
            }
          ></Route>
        </Routes>
    </>
  );
};

export default App;
```

{% enddetails %}

Once we have configured our app routes, we can test them out to ensure they are working as intended.

Open your browser and navigate to one of the URLs:

```
http://localhost:3000/signup
http://localhost:3000/signin
```

## Wrapping Up. <a name="wrapping-up"></a>

##### 🔝 [Go To TOC](#TOC)

We've now completed the front-end side of our project. The only thing left is implementing the backend, which we will work on in the following article. This will be where we store and retrieve data from our database. We will also use the backend to process requests from our front-end. However, implementing the backend would make the article dense and hard to digest. Therefore, it is a topic for a future article.

I hope you enjoyed this part of the series and that you found it helpful in understanding how to start using the FAR3 stack. If you have any questions or comments, please feel free to reach out to me on one of my socials via [my website](https://www.wiseai.dev/). Thank you for reading! Stay tuned!

You can checkout my repo on Github to use/modify the code used in this article to fit your needs:

{% embed https://github.com/wiseaidev/awesome-far3 %}

## References. <a name="references"></a>

##### 🔝 [Go To TOC](#TOC)

[0] <a name="0"></a> Mahmoud Harmouch. [The FARR Stack Manifesto.](https://dev.to/wiseai/the-farr-stack-manifesto-mcd). dev.to. Published 2022-08-30.

[1] <a name="1"></a> StackOverFlow. [React tag.](https://stackoverflow.com/tags). dev.to. Retrieved 2022-09-03.

[2] <a name="2"></a> FastAPI. [Parallel Burgers.](https://fastapi.tiangolo.com/img/async/parallel-burgers/parallel-burgers-01.png). fastapi.tiangolo.com. Retrieved 2022-09-03.

[3] <a name="3"></a> Redis. [Linear Scaling with Redis Enterprise.](https://redis.com/redis-enterprise/technology/linear-scaling-redis-enterprise/). redis.com. Retrieved 2022-09-03.

[4] <a name="4"></a> Redis. [RediSearch Aggregations.](https://redis.io/docs/stack/search/reference/aggregations/). redis.io. Retrieved 2022-09-03.

[5] <a name="5"></a> Henry Boisdequin. [React vs Vue vs Angular vs Svelte.](https://dev.to/hb/react-vs-vue-vs-angular-vs-svelte-1fdm). dev.to. Retrieved 2022-09-03.

[6] <a name="6"></a> ReactJS.org. [React v18.0](https://reactjs.org/). reactjs.org. Retrieved 2022-09-03.

[7] <a name="7"></a> redux.js.org. [Redux Application Data Flow](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow#redux-application-data-flow). redux.js.org. Retrieved 2022-09-03.