
Security News
/Company News
Socket Is Sponsoring Composer and Packagist
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.
@mongez/react-atom
Advanced tools
A powerful state management tool for React Js.
The main purpose of the birth of this package is to work with a simple and performant state management tool to handle data among components and outside components.
Redux is a powerful state management tool, the purpose of this package is to use a simple state management which provides a good performance with large applications.
yarn add @mongez/react-atom
Or
npm i @mongez/react-atom
Atoms are meant to be unique therefore the atom key can not be used in more than one atom, if other atom is being created with a previously defined atom, an error will be thrown that indicates to use another atom key.
Atoms can be accessed outside components, this is useful when you want to use the atom's value in a function or a class, or even in a service.
By embracing the idea using atoms outside components, we can easily manage the data in a single place, this can help you update or fetch the current atom's value while you're not using it inside a component.
The main idea here is every single data that might be manipulated will be stored independently in a shape of an atom.
This will raise the power of single responsibility.
import { atom, Atom } from "@mongez/react-atom";
export const currencyAtom: Atom<string> = atom({
key: "currency",
default: "EUR",
});
Please note that all atoms are immutables, the default data will be kept untouched if it is an object or an array.
When creating a new atom, it's recommended to pass the atom's value type as a generic type to the atom function, this will help you use the atom's value in a type-safe way.
Now the currencyAtom atom has only single value, from this point we can use it in anywhere in our application components or event outside components.
Header.tsx
import React from "react";
import { currencyAtom } from "~/src/atoms";
export default function Header() {
const currency = currencyAtom.useValue();
return (
<>
<h1>Header</h1>
Currency: {currency}
</>
);
}
Footer.tsx
import React from "react";
import { useAtom } from "@mongez/react-atom";
import { currencyAtom } from "~/src/atoms";
export default function Footer() {
const currency = currencyAtom.useValue();
return (
<>
<h1>Footer</h1>
You're using our application in {currency} Currency.
</>
);
}
In our Header component we just display the current value of the currency, which is the default value in our atom EUR.
In the Footer component, we also displayed the current currency in a form of a message.
Now let's add some buttons to change the current currency from the header.
Header.tsx
import React from "react";
import { useAtom } from "@mongez/react-atom";
import { currencyAtom } from "~/src/atoms";
export default function Header() {
const [currency, setCurrency] = currencyAtom.useState();
return (
<>
<h1>Header</h1>
Currency: {currency}
<button onClick={(e) => setCurrency("EUR")}>EUR</button>
<button onClick={(e) => setCurrency("USD")}>USD</button>
<button onClick={(e) => setCurrency("EGP")}>EGP</button>
</>
);
}
Once we click on any button of the three buttons, the currency will be changed in our atom, the good thing here is it will be changed in the Footer component as well.
Any atom must have a default value when initializing it, this value can be any type, it can be a string, number, boolean, object, array, however, when the default value is an object or an array, the atom gets a special treatment.
We will see this later in the documentation.
Atom's value can be fetched in different ways, depends what are you trying to do.
For example, if you're using the atom outside a React component or you're using it inside a component but don't want to rerender the component when the atom's value changes, you can use the atom.value property.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
console.log(currencyAtom.value); // get current value
Another way to get the atom's value when you're inside a React component, we can use atom.useValue() to get the atom's value and also trigger a component rerender when the atom's value changes.
import React from "react";
import { currencyAtom } from "~/src/atoms";
export default function Header() {
const currency = currencyAtom.useValue();
return (
<>
<h1>Header</h1>
Currency: {currency}
</>
);
}
The basic way to update atom's value is by using atom.update, this method receives the new value of the atom and updates it.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.update("USD"); // any component using the atom will be rerendered automatically.
We can also pass a callback to the update function, the callback will receive the old value and the atom instance.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.update((oldValue, atom) => {
// do something with the old value
return "USD";
});
Please do remember that
atom.updatemust receive a new reference of the value, otherwise it will not trigger the change event, for exampleatom.update({ ...user })will trigger the change event.
// /src/atoms/user-atom.ts
import { atom } from "@mongez/react-atom";
export type UserData = {
name: string;
email: string;
age: number;
id: number;
};
export const userAtom = atom<UserData>({
key: "user",
default: {
name: "Hasan",
age: 30,
email: "hassanzohdy@gmail.com",
id: 1,
},
});
Now if we want to make an update for the user atom using atom.update, it will be something like this:
// anywhere in your app
import { userAtom } from "~/src/atoms/user-atom";
userAtom.update({
...userAtom.value,
name: "Ahmed",
});
Or using callback to get the old value:
// anywhere in your app
import { userAtom } from "~/src/atoms/user-atom";
userAtom.update((oldValue) => {
return {
...oldValue,
name: "Ahmed",
};
});
Added in v3.2.0
Works exactly like update method, but it will not trigger the change event.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.silentUpdate("USD"); // any component using the atom will be rerendered automatically.
Added in V4.0.0
Works exactly like change method, but it will not trigger the change event.
// anywhere in your app
import { userAtom } from "~/src/atoms";
userAtom.silentChange("name", "Ahmed");
Added in v2.1.0
If the atom is an object atom, you can use atom.merge to merge the new value with the old value.
// src/atoms/user-atom.ts
import { atom } from "@mongez/react-atom";
export type UserData = {
name: string;
email: string;
age: number;
id: number;
};
export const userAtom = atom<UserData>({
key: "user",
default: {
name: "Hasan",
age: 30,
email: "hassanzohdy@gmail.com",
id: 1,
},
});
Now if we want to make an update for the user atom using atom.update, it will be something like this:
// anywhere in your app
import { userAtom } from "~/src/atoms";
userAtom.update({
...userAtom.value,
name: "Ahmed",
age: 25,
});
If you notice, we've to spread the old value and then add the new values, this is good, but we can use atom.merge instead.
// anywhere in your app
import { userAtom } from "~/src/atoms";
userAtom.merge({
name: "Ahmed",
age: 25,
});
This is just a shortcut for atom.update, it will merge the new value with the old value and then update the atom.
If you want to get the atom's value and update it at the same time, you can use atom.useState().
import React from "react";
import { currencyAtom } from "~/src/atoms";
export default function Header() {
const [currency, setCurrency] = currencyAtom.useState();
return (
<>
<h1>Header</h1>
Currency: {currency}
<button onClick={(e) => setCurrency("EUR")}>EUR</button>
<button onClick={(e) => setCurrency("USD")}>USD</button>
<button onClick={(e) => setCurrency("EGP")}>EGP</button>
</>
);
}
Works exactly like useState hook, the first item in the returned array is the current value of the atom, the second item is a state updater for the atom's value.
The main difference here is when the atom's value is changed from any other place, this component will be rerendered automatically.
Another super amazing feature here is to watch for a property of the atom's value if it's defined as an object.
import React from "react";
import { atom, Atom } from "@mongez/react-atom";
type User = {
name: string;
email: string;
age: number;
};
const userAtom = atom<User>({
key: "user",
default: {},
});
Now let's create a component to display the user's name and email.
import React from "react";
import { userAtom } from "~/src/atoms";
export default function User() {
const user = userAtom.useValue();
return (
<>
<h1>User</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</>
);
}
Now let's update the user's name from another component.
import React from "react";
import { userAtom } from "~/src/atoms";
export default function UserForm() {
const [user, setUser] = userAtom.useState();
return (
<>
<h1>User Form</h1>
<input
type="text"
value={user.name}
onChange={(e) => setUser({ ...user, name: e.target.value })}
/>
<input
type="text"
value={user.email}
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
</>
);
}
This is great, but what if we want to have a component that will be rerendered only when the user's name changes, not the email, here we can use atom.useWatcher to watch for a specific property of the atom's value.
import React from "react";
import { userAtom } from "~/src/atoms";
export default function User() {
const name = userAtom.useWatcher("name");
return (
<>
<h1>User</h1>
<p>Name: {name}</p>
</>
);
}
Now when the name property is changed, this component will be rerendered automatically, otherwise it won't.
To listen to atom when it is reset, use onReset method.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.onReset((atom) => {
//
});
This will be triggered after the update event is triggered
Using atom.use will merge both useValue and useWatcher methods into one.
If the use received a parameter, then it will be watching for the given property change, otherwise it will watch for the entire atom's value change.
Starting from version 2 and above,
atom.usewill be the recommended way to watch for atom's value changes for single property atoms instead ofuseWatcherasuseWatcherwill be removed in the next release.
type User = {
name: string;
age: number;
position: "developer" | "designer" | "manager";
notifications: number;
};
const userAtom = atom<User>({
key: "user",
default: {
name: "Hasan",
age: 25,
position: "developer",
},
});
// now in any component
import userAtom from "./userAtom";
export function Header() {
const notifications = userAtom.use("notifications");
return <header>{notifications}</header>;
}
This will only re-render the component when the notifications property changes.
Using use without any parameter will watch for the entire atom's value change.
type User = {
name: string;
age: number;
position: "developer" | "designer" | "manager";
notifications: number;
};
// now in any component
import userAtom from "./userAtom";
export function Header() {
const user = userAtom.useValue();
return <header>{user.notifications}</header>;
}
This will be rerendered when the entire atom's value changes.
type User = {
name: string;
age: number;
position: "developer" | "designer" | "manager";
notifications: number;
};
// now in any component
import userAtom from "./userAtom";
export function Header() {
const notifications = userAtom.use("notifications"); // will return number, and Typescript will complain if you try to use other properties
return <header>{notifications}</header>;
}
Instead of passing the whole object to the setUser function, we can pass only the key we want to change using atom.change function.
import React from "react";
import { userAtom } from "~/src/atoms";
export default function UserForm() {
const [user, setUser] = userAtom.useState();
return (
<>
<h1>User Form</h1>
<input
type="text"
value={user.name}
onChange={(e) => userAtom.change("name", e.target.value)}
/>
<input
type="text"
value={user.email}
onChange={(e) => userAtom.change("email", e.target.value)}
/>
</>
);
}
This will change only the given key in the atom's value, and trigger a component rerender if the atom's value is used in the component.
Please note that
changemethod callsupdatemethod under the hood, so it will generate a new object.
If atom's value is an object, we can get a value from the atom directly using atom.get function.
import { atom } from "@mongez/atom-react";
const userAtom = atom({
key: "user",
default: {
key: "Hasan",
address: {
city: "New York",
},
},
});
console.log(userAtom.get("key")); // Hasan
Dot Notation is also supported.
console.log(userAtom.get("address.city")); // New York
If key doesn't exist, return default value instead.
console.log(userAtom.get("email", "default@email.com")); // default@email.com
This feature might be useful in some scenarios when we need to reset the atom's value to its default value.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.reset(); // any component using the atom will be rerendered automatically.
This will trigger an atom update and set the atom's value to its default value.
Added in v3.2.0
Sometimes its useful to reset the atom's value to its default value without triggering the change event, this can be achieved using silentReset method, a good sue case for this is when a component is unmounted and you want to reset the atom's value to its default value without triggering the change event.
// Header.tsx
import { currencyAtom } from "~/src/atoms";
import { useEffect } from "react";
export default function Header() {
const currency = currencyAtom.useValue();
useEffect(() => {
return () => currencyAtom.silentReset();
}, []);
return (
<>
<h1>Header</h1>
Currency: {currency}
</>
);
}
This will not trigger the value change event, but it will reset the atom's value to its default value and the reset event will be triggered though
We can also destroy the atom using destroy() method from the atom, this will stop re-rendering any component that using the atom using useAtom or useAtomState hooks.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.destroy();
To get the atom key, use atom.key will return the atom key.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
console.log(currencyAtom.key); // currencyAtom
To list all registered atoms, use atomsList utility for that purpose.
// anywhere in your app
import { atomsList } from "~/src/atoms";
console.log(atomsList()); // [currencyAtom, ...]
Sometimes we may need to handle the atom.get function to get the data in a customized way, we can achieve this by defining in the atom function call how the atom will retrieve the object's value.
Without Defining the atom getter
const settingsAtom = atom({
key: "user",
default: {
isLoaded: false,
settings: {},
},
});
// later
settingsAtom.update({
isLoaded: true,
settings: {
websiteName: "My Website Name",
},
});
console.log(userAtom.get("settings.websiteName")); // My Website Name
After Defining it
import { atom } from "@mongez/atom-react";
const settingsAtom = atom({
key: "settings",
default: {
isLoaded: false,
settings: {},
},
get(key: string, defaultValue: any = null, atomValue: any) {
return atomValue[key] !== undefined
? atomValue[key]
: atomValue.settings[key] !== undefined
? atomValue.settings[key]
: defaultValue;
},
});
// later
settingsAtom.update({
isLoaded: true,
settings: {
websiteName: "My Website Name",
},
});
console.log(settingsAtom.get("websiteName")); // My Website Name
This is what happens with useAtom hook, it listens to the atom's value change using onChange method.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
currencyAtom.onChange((newValue, oldValue, atom) => {
//
});
Please note the
onChangeis returning an EventSubscription instance, we can remove the listener anytime, for example when unmounting the component.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
// in your component...
const [currency, setCurrency] = useState(currencyAtom.value);
useEffect(() => {
const onCurrencyChange = currencyAtom.onChange(setCurrency);
return () => onCurrencyChange.unsubscribe();
}, []);
Sometimes you may need to watch for only a key in the atom's value object, the atom.watch function is the perfect way to achieve this.
Please note this only works if the atom's default is an object or an array.
// anywhere in your app
import { atom } from "@mongez/react-atom";
const userAtom = atom({
key: "user",
default: {
key: "Hasan",
address: {
city: "New York",
},
},
});
userAtom.watch("key", (newName, oldName) => {
console.log(newName, oldName); // 'Hasan', 'Ali'
});
// later in the app
userAtom.update({
...userAtom.value,
key: "Ali",
});
Dot notation is allowed too.
// anywhere in your app
import { atom } from "@mongez/react-atom";
const userAtom = atom({
key: "user",
default: {
key: "Hasan",
address: {
city: "New York",
},
},
});
userAtom.watch("address.cty", (newCity, oldCity) => {
console.log(newName, oldName); // 'New York', 'Cairo'
});
// later in the app
userAtom.update({
...userAtom.value,
address: {
...userAtom.value.address,
city: "Cairo",
},
});
In some scenarios, we may need to watch for a key in the atom's value object for change and perform an action inside a component, the atom.useWatch hook is the perfect way to achieve this.
export function SomeComponent() {
const [city, setCity] = useState(userAtom.get("address.city"));
userAtom.useWatch("address.city", setCity);
// first time will render New York then it will render Cairo
return <>Current City: {city}</>;
}
Sometimes it's useful to mutate the value before updating it in the atom, this can be achieved via defining beforeUpdate method in the atom declaration.
This is very useful especially when dealing with objects/arrays and you want to make some operations before using the final value.
beforeUpdate(newValue: any, oldValue: any, atom: Atom)
import { atom, Atom } from "@mongez/react-atom";
export const multipleAtom: Atom = atom({
key: "multiple",
default: 0,
beforeUpdate(newNumber: number): number {
return newNumber * 2;
},
});
multipleAtom.update(4);
console.log(multipleAtom.value); // 8
To detect atom destruction when destroy() method, use onDestroy.
// anywhere in your app
import { currencyAtom } from "~/src/atoms";
const subscription = currencyAtom.onDestroy((atom) => {
//
});
We can get the type of the atom's value using atom.type property.
const currencyAtom = atom({
key: "currency",
default: "USD",
});
console.log(currencyAtom.type); // string
If the default value is an array it will be returned as array not object.
const todoListAtom = atom({
key: "todo",
default: [],
});
console.log(todoListAtom.type); // array
Works only if the atom's default value is array
We can get use of the following methods to make our life easier.
atom.addItem(item: any) => void
This method will allow you adding item to the array, it will also trigger the change event.
const todoListAtom = atom({
key: "todo",
default: [],
});
// SomeComponent.tsx
export function TodoList() {
const items = todoListAtom.useValue();
const addNewItem = () =>
todoListAtom.addItem({
title: "My first task",
id: 213,
}); // this will update the items and re-render the component again.
return (
<>
Total Items: {items.length}
<button onClick={addNewItem}>Add Item</button>
</>
);
}
atom.removeItem(index: number | (item: any, index: number) => number) => void
To remove an item from the atom's array we can use the removeItem method.
const todoListAtom = atom({
key: "todo",
default: [],
});
// SomeComponent.tsx
export function TodoList() {
const items = todoListAtom.useValue();
const addNewItem = () =>
todoListAtom.addItem({
title: "My first task",
id: 213,
}); // this will update the items and re-render the component again.
return (
<>
Total Items: {items.length}
<button onClick={addNewItem}>Add Item</button>
{items.map((item, index) => (
<div key={index}>
<div>Title: {item.title}</div>
<button onClick={() => todoListAtom.removeItem(index)}>Remove</button>
</div>
))}
</>
);
}
This will remove the item by the given index.
It can also be removed by passing a callback to remove the item from the list.
todoListAtom.removeItem((item) => item.id > 100);
Please note this will only remove the first matched item.
To remove multiple items, use removeItems method instead.
atom.removeItem(indexes: number[] | (item: any, index: number) => number) => void
Works exactly like removeItem except that it accepts an array of indexes or a callback function to remove multiple items.
todoListAtom.removeItems([0, 2, 3]); // will remove index 0, 2 and 3
// OR
todoListAtom.remoteItems((item) => item.id > 1);
atom.replaceItem(index: number, newItemValue: any) => void
Updates item's value by for the given index
const index = 2;
todoListAtom.replaceItem(2, {
title: "New Title",
});
atom.getItem(indexOrCallback: number | ((item: any, index: number) => any)) => any
Get an item from the array using item index or callback function.
const index = 2;
const item = todoListAtom.getItem(index);
// Or
const itemId = 15111; // dummy id
const otherItem = todoListAtom.getItem((item) => item.id === itemId);
atom.getItemIndex(callback: (item: any, index: number, array: any[]) => any) => any
Get the index of the first matched element to the given callback.
const itemId = 15111; // dummy id
const itemIndex = todoListAtom.getItemIndex((item) => item.id === itemId); // 2 for example
atom.map(callback: (item: any, index: number, array: any[]) => any) => any
Walk over every item in the array and update it, this will trigger the change event.
const numbersAtom = atom({
key: "number",
default: [1, 2, 3, 4],
});
// multiple the atom's array numbers by 2
numbersAtom.map((number) => number * 2);
console.log(numbersAtom.value); // [2, 4, 6, 8];
This can be useful feature when working with arrays or strings, atom.length returns the count of total elements/characters of the atom's current value.
const todoListAtom = atom({
key: "todo",
default: [],
});
console.log(todoListAtom.length); // 0
todoListAtom.addItem({
title: "My first task",
id: 213,
});
console.log(todoListAtom.length); // 1
Added in V3.0.0
Atom Provider allows you to use same atom in a scoped version, this is useful when you want to deal with an atom inside an array of objects, or using the same atom in multiple components in the same page but each atom handles different data.
Wrap the code that you want to use the atom inside it with AtomProvider, and pass the to the register prop
import { AtomProvider } from "@mongez/atom";
import { currencyAtom } from "~/src/atoms";
export default function MyComponent() {
return (
<AtomProvider register={[currencyAtom]}>
<ChildComponent />
</AtomProvider>
);
}
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("currency");
return (
<div>
<div>Value: {value}</div>
<button onClick={() => userAtom.change("name", "New Value")}>
Change Value
</button>
</div>
);
}
The main difference here you get a copy of the atom by calling useAtom, this will ensure that data are separated from the original atom, you get a new copy of the atom.
You may also register multiple atoms at once.
import { AtomProvider } from "@mongez/atom";
import currentAtom from "./currentAtom";
import userAtom from "./userAtom";
export default function App() {
return (
<AtomProvider register={[currentAtom, userAtom]}>
<App />
</AtomProvider>
);
}
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.
The argument passed to the useAtom hook is the atom name.
Added in V4.0.0
Now atoms can lay in SSR environments like Nextjs, Remix, etc, but with a little bit of change.
To make sure that the atom's value is being updated in both client and server, we need to create a special atom provider from the atom itself.
// src/atoms/user-atom.ts
import { atom } from "@mongez/atom";
type User = {
name: string;
email: string;
age: number;
id: number;
};
const userAtom = atom<User>({
key: "user",
default: {},
});
// very important is to create the UserAtomProvider
export const UserAtomProvider = userAtom.Provider;
We can not directly use userAtom.Provider in Nextjs as it will throw an error of not identifying it, so we need to export it in a separate const UserAtomProvider.
// src/app/page.tsx
import { UserAtomProvider } from "~/atoms/user-atom";
export default function Page() {
const userFromCookies = {};
return (
<UserAtomProvider value={userFromCookies}>
<OtherComponentsListHere />
</UserAtomProvider>
);
}
Now you can use the userAtom as usual in any component, it will be updated in both client and server.
Added in V3.1.0
Helper atoms functions allow you to easily manage variant atoms that you would probably use in your app.
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 (
<Modal isOpen={opened} onClose={close}>
<div>Login Content Here</div>
</Modal>
);
}
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 (
<div>
<button onClick={openLoginPopup}>Login</button>
</div>
);
}
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 (
<div>
<button onClick={loginPopupAtom.open}>Login</button>
</div>
);
}
So Before:
const open = loginPopupAtom.get("open");
After:
const open = loginPopupAtom.open;
This applies to close and toggle functions as well.
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 (
<div>
{isLoading && <div>Loading...</div>}
{posts.map((post) => (
<div>{post.title}</div>
))}
</div>
);
}
The
loadingAtomhas same functions asopenAtom, but instead ofopen,closeandtoggle, it hasstartLoading,stopLoadingandtoggleLoading.
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<Post[]>("posts");
Our atom is ready to be used, let's use it in our Posts component
src/components/Posts.tsx
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 (
<div>
{isLoading && <div>Loading...</div>}
{data && data.map((post) => <div>{post.title}</div>)}
{error && <div>{error.message}</div>}
</div>
);
}
Again, the exposed functions are used only with the helper atoms, like fetchingAtom, loadingAtom and openAtom.
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 (
<div>
<LoadingPosts />
<PostsList />
<PostsError />
</div>
);
}
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 <div>Loading...</div>;
}
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 (
<div>
{data.map((post) => (
<div>{post.title}</div>
))}
</div>
);
}
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 <div>{error.message}</div>;
}
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.
register prop to AtomProvider component.useWatcher hook.use now accepts only the key, to get the value use useValue hook instead.openAtom, loadingAtom and fetchingAtom, functions.merge method to atom.update typings.default type to accept empty object.useWatcher is now deprecated, use use instead.useAtom hook.useAtomValue hook.useAtomState hook.useAtomWatch hook.useAtomWatcher hook.getAtomValue function.name property from atom.actions.useState hook to atom.atom typings.beforeUpdate now receives the old value as second argument and the atom object as third argument.useValue method.atom.get method.Atom.useWatcheruseWatcher and useWatch embedded in the atom itself.FAQs
A simple state management tool for React Js.
The npm package @mongez/react-atom receives a total of 102 weekly downloads. As such, @mongez/react-atom popularity was classified as not popular.
We found that @mongez/react-atom demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
/Company News
Socket has joined the new Composer and Packagist sponsorship program as a launch sponsor, supporting the team that keeps PHP's package ecosystem secure.

Research
/Security News
Benign-looking npm packages split malicious functionality across a dependency chain that deploys a cross-platform RAT targeting Alibaba developers.

Research
/Security News
Two Joyfill npm beta releases contain an import-time implant that uses blockchain transactions to retrieve a remote-access trojan.