Mongez Atom Part-7

Mohamed Eltahawy
Oct 29, 2023
6 min read
post_comment0 Comments
post_like0 Likes

#SSR Support

Added in V3.0.0


Now atoms can lay in SSR environments like Nextjs, Remix, etc, but with a little bit of change.

In your base app project, wrap your app with AtomProvider component.

import { AtomProvider } from "@mongez/atom"; export default function App() { return ( ); }

Then in your pages, wrap your page component with AtomProvider component.

Now to access any atom from any component wrapped inside AtomProvider component, you need to use useAtom hook.

import { useAtom } from "@mongez/atom"; export default function Page() { const userAtom = useAtom("user"); return (
Value: {value}
); }

The main difference here you get a copy of the atom by calling useAtom, this will ensure that on each page request, you get a new copy of the atom, and the atom will be updated only for the current request.

Do not use the original atom inside SSR apps, use useAtom and pass to it the atom's key.

You can also register atoms in the provider using register prop, it receives an array of atoms

import { AtomProvider } from "@mongez/atom"; import currentAtom from "./currentAtom"; import userAtom from "./userAtom"; export default function App() { return ( ); }

Because atoms are auto registered when the atom's file is being imported (when declaring an atom), this happens when the atom is being imported, but now we are using useAtom instead of the atom itself, thus we need to register the atom as well.

#Helper Atoms

Added in V3.1.0

Helper atoms functions allow you to easily manage variant atoms that you would probably use in your app.

#Open Atom

The openAtom function is mainly used to manage an open state, this one is useful when working with modals, popups, etc.

import { openAtom } from "@mongez/atom"; export const loginPopupAtom = openAtom("openAtom");

This atom exposes 4 values:

  • opened: boolean value that indicates if the popup is opened or not.
  • open: a function that sets the opened value to true.
  • close: a function that sets the opened value to false.
  • toggle: a function that toggles the opened value.

By default, opened is set to false, if you want to set it to true by default, pass true as the second argument to booleanAtom function.

import { openAtom } from "@mongez/atom"; export const loginPopupAtom = openAtom("loginPopup", true);

Let's see an example of usage

LoginPopup.tsx

import { loginPopupAtom } from "./atoms"; export default function LoginPopup() { const opened = loginPopupAtom.use("opened"); // watch for opened when it is changed const close = loginPopupAtom.get("close"); // use `get` not `use` function to get the function return (
Login Content Here
); }

Header.tsx

import { loginPopupAtom } from "./atoms"; export default function Header() { const openLoginPopup = loginPopupAtom.get("open"); // use `get` not `use` function to get the function return (
); }

As you can see in the above example, we used get function to get the open and close functions, this is because we don't want to watch for these functions, they are static functions, no changes will occur to them.

The opened value is watched for changes, so when the popup is opened or closed, the LoginPopup component will be re-rendered.

This works exactly like a normal atom, but, we can go more easier by using the atom actions directly, like open, close and toggle.

import { loginPopupAtom } from "./atoms"; export default function Header() { return (
); }

So Before:

const open = loginPopupAtom.get("open");

After:

const open = loginPopupAtom.open;

This applies toclose and toggle functions as well.

#Loading Atom

Another good helper function is loadingAtom which is used to manage a loading state, this is useful when you want to show a loading indicator when a request is being made.

It has 3 values:

  • isLoading: boolean value that indicates if the request is being made or not.
  • startLoading: a function that sets the isLoading value to true.
  • stopLoading: a function that sets the isLoading value to false.
  • toggleLoading: a function that toggles the isLoading value.

By default, isLoading is set to false, if you want to set it to true by default, pass true as the second argument to loadingAtom function.

import { loadingAtom } from "@mongez/atom"; export const loadingPostsAtom = loadingAtom("loadingPosts", true);

Let's see an example of usage

Posts.tsx

import { loadingPostsAtom } from "./atoms"; import { useEffect, useState } from "react"; import { loadPosts } from "./api"; export default function Posts() { const [posts, setPosts] = useState([]); const isLoading = loadingPostsAtom.use("isLoading"); // watch for isLoading when it is changed useEffect(() => { loadingPostsAtom.startLoading(); loadPosts().then((response) => { loadingPostsAtom.stopLoading(); setPosts(response.data.posts); }); }, []); return (
{isLoading &&
Loading...
} {posts.map((post) => (
{post.title}
))}
); }

The loadingAtom has same functions as openAtom, but instead of open, close and toggle, it has startLoading, stopLoading and toggleLoading.

#Fetching Atom

This helper atom is quiet good actually, it allows you to manage an API fetching, consider it a full atom that manages the loading state, the data, and the error.

It exposes 8 values:

  • isLoading: boolean value that indicates if the request is being made or not, default value is false.
  • startLoading: a function that sets the isLoading value to true.
  • stopLoading: a function that sets the isLoading value to false.
  • data: the data returned from the API, default value is null.
  • pagination: the pagination returned from the API, default value is null.
  • error: the error returned from the API, default value is null.
  • success: A function that sets the data value and sets the isLoading value to false.
  • failed: A function that sets the error value and sets the isLoading value to false.
  • append: A function that works only if data is array, it appends the new data to the existing data.
  • prepend: A function that works only if data is array, it prepends the new data to the existing data.

Let's use the previous example of posts but this time with fetchingAtom

src/atoms/posts-atom.ts

import { fetchingAtom } from "@mongez/atom"; export type Post = { id: number; title: string; body: string; }; // define the post type as an array for better type checking export const postsAtom = fetchingAtom("posts");

Our atom is ready to be used, let's use it in our Posts component

import { postsAtom } from "../atoms/posts-atom"; import { useEffect } from "react"; export default function Posts() { const isLoading = postsAtom.use("isLoading"); // watch for isLoading when it is changed const data = postsAtom.use("data"); // watch for data when it is changed const error = postsAtom.use("error"); // watch for error when it is changed useEffect(() => { postsAtom.startLoading(); loadPosts() .then((response) => { postsAtom.success(response.data.posts, response.data.pagination); }) .catch((error) => { postsAtom.failed(error); }); }, []); return (
{isLoading &&
Loading...
} {data && data.map((post) =>
{post.title}
)} {error &&
{error.message}
}
); }

Again, the exposed functions are used only with the helper atoms, like fetchingAtom, loadingAtom and openAtom.

#Best Practices With Atoms

Atoms have two main objectives, a triggering atom update and a listening for changes, so it is always better to separate any component that is going to be only the updating component from the component that is going to listen for changes.

In the login example, we have put the loginPopup update in the Header component, when user clicks on the login button, it will trigger atom update but the Header component is not interested in listening for changes, it is only interested in triggering the update so it will not re-render, in the meanwhile, the LoginPopup component is interested in listening for changes, so it will re-render when the atom is updated.

Let's put this into action, in the fetchingAtom example, we used triggering and listening values in the same component, let's separate them.

src/components/Posts.tsx

import { postsAtom } from "../atoms/posts-atom"; import { useEffect } from "react"; import LoadingPosts from "./LoadingPosts"; import PostsList from "./PostsList"; import PostsError from "./PostsError"; export default function Posts() { useEffect(() => { postsAtom.startLoading(); loadPosts() .then((response) => { postsAtom.success(response.data.posts); }) .catch((error) => { postsAtom.failed(error); }); }, []); return (
); }

Now we have separated the triggering component from the listening components, this will make the Posts component only responsible for triggering the atom update, and the LoadingPosts, PostsList and PostsError components are only responsible for listening for changes.

Let's create these components

src/components/LoadingPosts.tsx

import { postsAtom } from "../atoms/posts-atom"; export default function LoadingPosts() { const isLoading = postsAtom.use("isLoading"); // watch for isLoading when it is changed if (!isLoading) { return null; } return
Loading...
; }

src/components/PostsList.tsx

import { postsAtom } from "../atoms/posts-atom"; export default function PostsList() { const data = postsAtom.use("data"); // watch for data when it is changed if (!data) { return null; } return (
{data.map((post) => (
{post.title}
))}
); }

src/components/PostsError.tsx

import { postsAtom } from "../atoms/posts-atom"; export default function PostsError() { const error = postsAtom.use("error"); // watch for error when it is changed if (!error) { return null; } return
{error.message}
; }

Using this approach, Posts component will not re-render when the atom is updated, this will make it render only once, each other component will be rendered for first time, then based on the atom changes, each component will start interacting.

For example the LoadingPosts component will be rendered for first time, then when calling startLoading method, it will re-render again, but the Posts component will not re-render because it is not listening for isLoading changes.

END OF THE GREATEST STATE MANAGEMENT IN THE WORLD

check more about it in Atom

You are not logged in.