@ibnlanre/portal
v3.24.0-beta
Published
React state management library
Downloads
331
Maintainers
Readme
@ibnlanre/portal
Inspired by React Holmes and Tanstack Query, @ibnlanre/portal
is a simple application state management library for managing component state on a global level.
Table of Contents
Installation
To install @ibnlanre/portal
, you can use npm or yarn. Run the following command in your project directory:
npm install @ibnlanre/portal
or
yarn add @ibnlanre/portal
API
The following is an overview of the utility functions and hooks available in the portal module.
| Function | Description |
|--------------------|------------------------------------------------------------------|
| atom
| A utility for creating isolated states outside a component |
| usePortal
| Create a portal for accessing and updating states |
| usePortal.local
| A hook to persist state in Local Storage |
| usePortal.session
| A hook to persist state in Session Storage |
| usePortal.cookie
| A hook to persist state in document.cookie
|
| createBuilder
| Create a builder object for defining keys and values |
| cookieStorage
| An object representing the Cookie Storage API |
| debounceEffect
| A utility for creating a debounced effect in React |
Usage
Import the necessary functions and hooks
This library exports the following APIs to enhance state management and facilitate state manipulation
import {
atom,
createBuilder,
cookieStorage,
usePortal,
debounceEffect
} from "@ibnlanre/portal";
To create a portal
for managing state, use the usePortal
function
Here is an example:
// Setting an initial state is optional.
const [name, setName] = usePortal("client", {
state: {
name: "John Doe",
age: 54,
}
})
The state can also be retrieved from a browser store. A good practice is to define the get
function before the set
function, because of type inference.
const [token, setToken] = usePortal("token", {
// Fallback initial state
state: "",
// Get initial state from a persistent storage.
get: (state) => {
const value = cookieStorage.getItem("token");
if (value) return JSON.parse(value) as string
return state;
},
// The set method is called when the state changes.
// As well as, upon instantiation.
set: (value) => {
const state = JSON.stringify(value);
cookieStorage.setItem("token", state);
},
});
To create a typed portal
with a defined store, you can use the usePortal.make
function
This allows you to manage and access the store value outside of a React component. Here's an example of how to use usePortal.make
:
// Create a store for type safety
const store = {
foo: {
bar: {
baz: "qux"
},
rim: "raf"
},
};
// Create the portal outside the React Component,
// so that it can be exported and used elsewhere.
export const useStorePortal = usePortal.make(store);
// Manage and access the store value
const [state, setState] = useStorePortal("foo");
Persist the state by utilizing browser storage mechanisms
To persist the state in localStorage
:
const [state, setState] = useStorePortal.local("foo.bar");
To persist the state in sessionStorage
:
const [state, setState] = useStorePortal.session("foo.bar.baz");
To persist the state in document.cookie
:
const [state, setState] = useStorePortal.cookie("foo.rim", {
path: "/"
});
To manage state outside of a React Component, create an atom
An atom is a standalone state container that can be accessed and modified from anywhere in your application. Here's an example of creating an atom:
// Atoms should be created outside React Components
const counterAtom = atom({ state: 9 });
To access the value of an atom within a component, you can use the following code:
// An atom state is isolated from the portal system and can be accessed
// by explicitly exporting and importing the atom from where it was declared.
const [counter, setCounter] = counterAtom.use();
This following code snippet demonstrates an advanced example using TypeScript. It defines two atoms, messagesAtom
and userAtom
, which are part of a state management system.
const messagesAtom = atom({
state: {} as Messages,
events: {
get: ({ value }) => value?.messages?.at(0)?.last_24_hr_data,
set: ({ value }) => decrypt(value),
},
});
messagesAtom
is initialized with an empty object as its state and has two events:
get
: Retrieves thelast_24_hr_data
property from themessages
object.set
: Decrypts the provided value before setting it as the new state.
export const userAtom = atom({
state: {} as UserData,
events: {
set: ({ value }) => decrypt(value),
use: ({ on, set, ctx }, user: string) => {
const { getUrl } = ctx;
const ws = new WebSocket(getUrl(user));
ws.onmessage = ((value) => set(JSON.parse(value.data)));
on.rerun(() => {
if (ws.readyState === WebSocket.OPEN) ws.close();
})
},
},
context: {
getUrl: (user: string) => {
return builders.use().socket.users(user);
},
},
});
userAtom
is initialized with an empty object as its state and has three events:
set
: Decrypts the provided value before setting it as the new state.use
: Accepts auser
string parameter and establishes a WebSocket connection using thegetUrl
function from the context. It listens for incoming messages and updates the state accordingly. It also closes the WebSocket connection when theon
event is rerun.context
: Provides agetUrl
function that returns a URL based on theuser
parameter.
// Atoms are typically used within the context of a React component
const [messages, setMessages] = messagesAtom.use();
const [users, setUsers] = userAtom.use({ useArgs: [messages.user] });
To create a builder
pattern for property access
To create a nested record with a key
and value
pair, you can use the following code:
const store = {
foo: {
baz: (id: number) => `/bazaar/${id}`,
bar: 10,
},
};
const builder = createBuilder(store);
To access the keys of the builder
object, you can use the following code:
// `use` expects that the required arguments are passed.
builder.foo.baz.use(11); // ["foo", "baz", 11]
// `get` retrieves the keys without invoking the function.
builder.foo.baz.get(); // ["foo", "baz"]
// `get` also allows you to add more keys
builder.foo.baz.get("test"); // ["foo", "baz", "test"]
To retrieve nested values
, you can use the following code:
builder.use(); // store
builder.use().foo.baz(12); // "/bazaar/12"
builder.use().foo.bar; // 10
To add a prefix to the keys, you can use the following code:
const builderWithPrefix = createBuilder(store, "tap", "root");
builderWithPrefix.foo.bar.use() // ["tap", "root", "foo", "bar"]
To get the type of object passed to the builder
const builder = createBuilder(store);
// Note that the actual value of builder.is is undefined.
type StoreType = typeof builder.is;
// Ease of use accessing the type of nested properties.
type BarType = typeof builder.is.foo.bar;
Author
Ridwan Olanrewaju, root.i.ng, @ibnlanre
Contributions
All contributions are welcome and appreciated. Thanks for taking the time to contribute to @ibnlanre/portal
💚
Release
git add . # stages the changes made.
yarn package # builds and deploy.
License
This library is licensed under the MIT License.
Feel free to customize the content according to your needs. But, do leave a shoutout. Thanks! 😊.