Manage the state of your React app with Jotai

Managing the global state of a web application is one of the biggest challenges we face today. Although we have several solutions, I think the biggest problem is that we use certain libraries that need a huge boilerplate even if you need to make a small change.

One of the libraries that makes life easier for me in my opinion is Jotai. Which in my opinion has an approach that greatly simplifies managing the global states of our applications.

Other libraries already take the worry out of how you should structure our react components, but on the other hand they force us to structure our stores. However with Jotai it's super simple, you declare one thing or another and start using it (it's literally like that).

When I use a library that needs a lot of boilerplate and a whole structure, if the project has a big scale, it becomes very difficult to debug our applications. Or if you want to add the component's local state to the global state, it becomes very difficult. However with Jotai, these problems are solved so easily that using other libraries becomes frustrating.

One of the points that I find advantageous is that if you are already familiar with the useState() hook, you will use Jotai in a natural way.

Let's code

Today we are going to add the form values directly to the store and then on a second page we will show the data that was entered by us.

First let's install the dependencies:

npm i react-router-dom jotai

Now let's start by adding our routes:

// @src/App.jsx

import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";

import { Home, Profile } from "./pages";

const App = () => {
  return (
    <Router>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </Router>
  );
};

export default App;

As you may have noticed, we have two routes and each of them has its component, however these still have to be created in the pages folder. Let's start by working on the Home page:

// @src/pages/Home.jsx

import React from "react";

const Home = () => {
  return (
    <>
      <h2>Lets Get Started</h2>
      <form>
        <input
          placeholder="romaji"
          name="romaji"
          type="text"
          required
        />
        <input
          placeholder="format"
          name="format"
          type="text"
          required
        />
        <button type="submit">Register</button>
      </form>
    </>
  );
};

export default Home;

Now that we have the form for the Home page, we can start working on our store. First let's import the atom() function so we can store the form data. And basically atoms hold our source of truth for our application, being exported individually and must hold an initial value.

// @src/store.js
import { atom } from "jotai";

export const manhwaAtom = atom({
  romaji: "",
  format: "",
});

Going back to our Home page again, let's import jotai's useAtom() hook so we can read and mutate our atom. Then we also import our manhwaAtom from our store.

// @src/pages/Home.jsx

import React from "react";
import { useAtom } from "jotai";

import { manhwaAtom } from "../store";

const Home = () => {
  const [state, setState] = useAtom(manhwaAtom);
  return (
    // Hidden for simplicity
  );
};

export default Home;

Now just do what you normally do when working with the useState() hook. But of course using Jotai.

// @src/pages/Home.jsx

import React from "react";
import { useAtom } from "jotai";

import { manhwaAtom } from "../store";

const Home = () => {
  const [state, setState] = useAtom(manhwaAtom);
  const handleOnChange = (e) => {
    const { name, value } = e.target;
    setState({ ...state, [name]: value });
  };
  const handleOnSubmit = (e) => {
    e.preventDefault();
  };
  return (
    <>
      <h2>Lets Get Started</h2>
      <form onSubmit={handleOnSubmit}>
        <input
          placeholder="romaji"
          name="romaji"
          type="text"
          value={state.romaji}
          onChange={handleOnChange}
          required
        />
        <input
          placeholder="format"
          name="format"
          type="text"
          value={state.format}
          onChange={handleOnChange}
          required
        />
        <button type="submit">Register</button>
      </form>
    </>
  );
};

export default Home;

As you can see, I believe the code above is very similar to what they normally do. Now just redirect the user to the Profile page as soon as the form is submitted, using the react router's useHistory() hook.

// @src/pages/Home.jsx

import React from "react";
import { useAtom } from "jotai";
import { useHistory } from "react-router-dom";

import { manhwaAtom } from "../store";

const Home = () => {
  const { push } = useHistory();
  const [state, setState] = useAtom(manhwaAtom);
  const handleOnChange = (e) => {
    const { name, value } = e.target;
    setState({ ...state, [name]: value });
  };
  const handleOnSubmit = (e) => {
    e.preventDefault();
    push("/profile");
  };
  return (
    <>
      <h2>Lets Get Started</h2>
      <form onSubmit={handleOnSubmit}>
        <input
          placeholder="romaji"
          name="romaji"
          type="text"
          value={state.romaji}
          onChange={handleOnChange}
          required
        />
        <input
          placeholder="format"
          name="format"
          type="text"
          value={state.format}
          onChange={handleOnChange}
          required
        />
        <button type="submit">Register</button>
      </form>
    </>
  );
};

export default Home;

Now we can start working on our Profile page. On this page we are just going to read the data we have on our manhwaAtom. If the user decides to go back, we will reset our atom.

As the code is very similar to the previous one, I'll give you the final code for the Profile page:

// @src/pages/Profile.jsx

import React from "react";
import { useAtom } from "jotai";
import { useHistory } from "react-router";

import { manhwaAtom } from "../store";

const Profile = () => {
  const { push } = useHistory();
  const [state, setState] = useAtom(manhwaAtom);
  const handleReset = (e) => {
    e.preventDefault();
    setState({ romaji: "", format: "" });
    push("/");
  };
  return (
    <>
      <img src="https://bit.ly/3AfK4Qq" alt="anime gif" />
      <h2>
        <code>{JSON.stringify(state, null, "\t")}</code>
      </h2>
      <button onClick={handleReset}>Reset</button>
    </>
  );
};

export default Profile;

Now all that's left is to create the index file in the pages folder, to facilitate the import of components in App.jsx. Like this:

// @src/pages/index.js

export { default as Home } from "./Home";
export { default as Profile } from "./Profile";

The final result of the application should look like this:

I hope it helped and that it was easy to understand! 😁
Have a nice day! 😉

22