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! 😉
Top comments (2)
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?
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.