Skip to content

Commit

Permalink
Update portal implementation and utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ibnlanre committed Dec 27, 2023
1 parent ab438ec commit 3090799
Show file tree
Hide file tree
Showing 15 changed files with 237 additions and 339 deletions.
37 changes: 17 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ The following is an overview of the utility functions and hooks available in the
| 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 |
| `portal` | Create a portal for accessing and updating states |
| `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

Expand All @@ -52,18 +49,18 @@ import {
atom,
createBuilder,
cookieStorage,
usePortal,
portal,
debounceEffect
} from "@ibnlanre/portal";
```

### To create a `portal` for managing state, use the `usePortal` function
### To create a `portal` for managing state, use the `portal` function

Here is an example:

```typescript
// Setting an initial state is optional.
const [name, setName] = usePortal("client", {
const [name, setName] = portal.use("client", {
state: {
name: "John Doe",
age: 54,
Expand All @@ -74,7 +71,7 @@ const [name, setName] = usePortal("client", {
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.

```typescript
const [token, setToken] = usePortal("token", {
const [token, setToken] = portal.use("token", {
// Fallback initial state
state: "",

Expand All @@ -94,13 +91,13 @@ const [token, setToken] = usePortal("token", {
});
```

### To create a typed `portal` with a defined store, you can use the `usePortal.make` function
### To create a typed `portal` with a defined store, you can use the `portal.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`:
This allows you to manage and access the store value outside of a React component. Here's an example of how to use `portal.make`:

```typescript
// Create a store for type safety
const store = {
const record = {
foo: {
bar: {
baz: "qux"
Expand All @@ -109,32 +106,32 @@ const store = {
},
};

// Create the portal outside the React Component,
// Make typed portals outside of React Components,
// so that it can be exported and used elsewhere.
export const useStorePortal = usePortal.make(store);
export const store = portal.make(record);

// Manage and access the store value
const [state, setState] = useStorePortal("foo");
const [state, setState] = store.use("foo");
```

### Persist the state by utilizing browser storage mechanisms

To persist the state in `localStorage`:

```typescript
const [state, setState] = useStorePortal.local("foo.bar");
const [state, setState] = store.local("foo.bar");
```

To persist the state in `sessionStorage`:

```typescript
const [state, setState] = useStorePortal.session("foo.bar.baz");
const [state, setState] = store.session("foo.bar.baz");
```

To persist the state in `document.cookie`:

```typescript
const [state, setState] = useStorePortal.cookie("foo.rim", {
const [state, setState] = store.cookie("foo.rim", {
path: "/"
});
```
Expand Down
10 changes: 5 additions & 5 deletions src/builder/createBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Builder, KeyBuilder } from "@/definition";
*
* @template T The type of the object.
*
* @param {T} obj The object to traverse and retrieve the nested keys.
* @param {T} register The object to traverse and retrieve the nested keys.
* @param {string[]} [prefix=[]] An optional prefix to prepend to keys array in the builder object.
*
* @returns {Builder<T>} A builder object with callable functions representing the nested keys.
Expand All @@ -16,11 +16,11 @@ import type { Builder, KeyBuilder } from "@/definition";
export function createBuilder<
T extends Record<string, any>,
P extends readonly string[] = []
>(obj: T, ...prefix: P): Builder<T, P> {
const keys = Object.keys(obj) as Array<keyof T>;
>(register: T, ...prefix: P): Builder<T, P> {
const keys = Object.keys(register) as Array<keyof T>;

const builder = keys.reduce((acc, key) => {
const value = obj[key];
const value = register[key];
const newPath = prefix ? prefix.concat([key as string]) : [key as string];

if (typeof value === "function") {
Expand Down Expand Up @@ -55,5 +55,5 @@ export function createBuilder<
};
}, {} as KeyBuilder<T>);

return Object.assign({ use: () => obj }, builder) as Builder<T, P>;
return Object.assign({ use: () => register }, builder) as Builder<T, P>;
}
32 changes: 26 additions & 6 deletions src/definition/portal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export interface UsePortal<Store extends Record<string, any>> {
* @template Data The type of the data.
*
* @param {Path} path The path to the store value.
* @returns {PortalState<State>} A tuple containing the current state and a function to update the state.
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
local<
Path extends Paths<Store>,
Expand All @@ -239,7 +239,7 @@ export interface UsePortal<Store extends Record<string, any>> {
* @template Data The type of the data.
*
* @param {Path} path The path to the store value.
* @returns {PortalState<State>} A tuple containing the current state and a function to update the state.
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
session<
Path extends Paths<Store>,
Expand All @@ -260,7 +260,7 @@ export interface UsePortal<Store extends Record<string, any>> {
* @param {Path} path The path to the store value.
* @param {CookieOptions} cookieOptions The options for the cookie.
*
* @returns {PortalState<State>} A tuple containing the current state and a function to update the state.
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
cookie<
Path extends Paths<Store>,
Expand All @@ -272,6 +272,26 @@ export interface UsePortal<Store extends Record<string, any>> {
): PortalState<State, Data>;
}

/**
* Represents the properties of the `usePortalImplemenation` hook.
*
* @template State The type of the state.
* @template Path The type of the path.
* @template Store The type of the store.
* @template Data The type of the data.
*/
export interface UsePortalImplementation<
Store extends Record<string, any>,
Path extends Paths<Store>,
State extends GetValueByPath<Store, Path>,
Data
> {
path: Path;
store: Portal;
initialState?: State;
options?: PortalOptions<State, Data>;
}

/**
* Represents the properties of the `useLocalImplementation` hook.
*
Expand All @@ -287,7 +307,7 @@ export interface UseLocalImplementation<
Data
> {
path: Path;
portal: Portal;
store: Portal;
config?: Config<State, Data>;
initialState: State;
}
Expand All @@ -307,7 +327,7 @@ export interface UseSessionImplementation<
Data
> {
path: Path;
portal: Portal;
store: Portal;
initialState: State;
config?: Config<State, Data>;
}
Expand All @@ -327,7 +347,7 @@ export interface UseCookieImplementation<
Data
> {
path: Path;
portal: Portal;
store: Portal;
initialState: State;
config?: CookieConfig<State, Data>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/portal/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./behaviorSubject";
export * from "./usePortalImplementation";
export * from "./usePortal";
export * from "./makePortal";
export * from "./portal";
140 changes: 140 additions & 0 deletions src/portal/makePortal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
Config,
CookieConfig,
GetValueByPath,
Paths,
PortalOptions,
} from "@/definition";
import { getResolvedState, getValue } from "@/utilities";

import { Portal } from "./portal";
import { useCookieImplementation } from "./useCookieImplementation";
import { useLocalImplementation } from "./useLocalImplementation";
import { usePortalImplementation } from "./usePortalImplementation";
import { useSessionImplementation } from "./useSessionImplementation";

function makePortal<Store extends Record<string, any>>(register: Store) {
/**
* Represents a mapping of keys (stringified) to portal entries.
* @type {PortalMap}
*/
const store = new Portal();

/**
* A map of portal entries.
* @type {PortalMap}
*/
const portal = {
/**
* A hook for managing the portal states.
*
* @param path The path of the portal's state
* @param {PortalOptions<State>} [options] The options of the portal's state
*
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
use: function usePortalWithStore<
Path extends Paths<Store>,
State extends GetValueByPath<Store, Path>,
Data = State
>(path: Path, options?: PortalOptions<State, Data>) {
const initialState = options?.state
? getResolvedState(options.state)
: getValue(register, path);

return usePortalImplementation<Store, Path, State, Data>({
path,
initialState,
options,
store,
});
},

/**
* A hook for managing the portal states with local storage.
*
* @param path The path of the portal's state
* @param {Config<State>} [config] The config of the portal's state
*
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
local: function useLocalWithStore<
Path extends Paths<Store>,
State extends GetValueByPath<Store, Path>,
Data = State
>(path: Path, config?: Config<State, Data>) {
const initialState = config?.state
? getResolvedState(config.state)
: getValue(register, path);

return useLocalImplementation<Store, Path, State, Data>({
path,
initialState,
config,
store,
});
},

/**
* A hook for managing the portal states with session storage.
*
* @param path The path of the portal's state
* @param {Config<State>} [config] The config of the portal's state
*
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
session: function useSessionWithStore<
Path extends Paths<Store>,
State extends GetValueByPath<Store, Path>,
Data = State
>(path: Path, config?: Config<State, Data>) {
const initialState = config?.state
? getResolvedState(config.state)
: getValue(register, path);

return useSessionImplementation<Store, Path, State, Data>({
path,
initialState,
config,
store,
});
},

/**
* A hook for managing the portal states with cookie storage.
*
* @param path The path of the portal's state
* @param {CookieConfig<State>} [config] The config of the portal's state
*
* @returns {PortalState<State, Data>} A tuple containing the current state and a function to update the state.
*/
cookie: function useCookieWithStore<
Path extends Paths<Store>,
State extends GetValueByPath<Store, Path>,
Data = State
>(path: Path, config?: CookieConfig<State, Data>) {
const initialState = config?.state
? getResolvedState(config.state)
: getValue(register, path);

return useCookieImplementation<Store, Path, State, Data>({
path,
initialState,
config,
store,
});
},

make: makePortal,
entries: store.entries,
removeItem: store.removeItem,
hasItem: store.hasItem,
getItem: store.getItem,
setItem: store.setItem,
clear: store.clear,
};

return portal;
}

export const portal = makePortal({} as Record<string, any>);
Loading

0 comments on commit 3090799

Please sign in to comment.