DEV Community

Cover image for Manage the state of your React app with Jotai
Francisco Mendes
Francisco Mendes

Posted on

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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: "",
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

The final result of the application should look like this:

final app

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

Top comments (2)

Collapse
 
dylanwatsonsoftware profile image
Dylan Watson • Edited

Yeah nice write-up! I've been playing with jotai lately and it seems really nice to use and has nice typescript integration. I also found it was better as type inference, so required less explicit types than zustand.

What's your thoughts about how best to carve up your application state? Would you expect many different atoms for individual primitives in your app? Or a single object that represents your whole state, as in redux and zustand?

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

I like to use Jotai when I want to isolate the state into several small pieces. As mentioned, it is quite simple to observe and persist the state of our applications. It also has great integration with TypeScript.

One of the things I love is that it feels like such a natural extension to React because it's so similar to useState and because it's so simple to work with.

In the example of this article I didn't do justice to the modularity and flexibility of Jotai, but as I said at the beginning, I usually split my state into several different atoms.