UNPKG

react-native-global-state-hooks

Version:

This is a package to easily handling global-state across your react-native-components No-redux... The library now includes @react-native-async-storage/async-storage to persist your state across sessions... if you want to keep using the old version without

401 lines (286 loc) β€’ 14.9 kB
# react-native-global-state-hooks 🌟 ![Image John Avatar](https://raw.githubusercontent.com/johnny-quesada-developer/global-hooks-example/main/public/avatar2.jpeg) Effortless **global state management** for React & React Native! πŸš€ Define a **global state in just one line of code** and enjoy **lightweight, flexible, and scalable** state management. Try it now on **[CodePen](https://codepen.io/johnnynabetes/pen/WNmeGwb?editors=0010)** and see it in action! ✨ --- ## πŸ”— Explore More - **[Live Example](https://johnny-quesada-developer.github.io/global-hooks-example/)** πŸ“˜ - **[Video Overview](https://www.youtube.com/watch?v=1UBqXk2MH8I/)** πŸŽ₯ - **[react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)** compatible with both `React & React Native` - **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** specific for web applications (**local-storage integration**). - **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** specific for React Native projects (**async-storage integration**). --- ## πŸš€ React Hooks Global States - DevTools Extension React Hooks Global States includes a dedicated, `devTools extension` to streamline your development workflow! Easily visualize, inspect, debug, and modify your application's global state in real-time right within your browser. ### πŸ”— [Install the DevTools Extension for Chrome](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee/preview?hl=en&authuser=0) ### πŸ“Έ DevTools Highlights | **Track State Changes** | **Modify the State** | | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | ![Track State Changes](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/track-state-changes.png) | ![Modify the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/modify-the-state.png) | | Effortlessly monitor state updates and history. | Instantly edit global states directly from the extension. | --- | **Restore the State** | **Custom Actions Granularity** | | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | ![Restore the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/restore-the-state.png) | ![Custom Actions Granularity](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/custom-actions-granularity.png) | | Quickly revert your application to a previous state. | Precisely debug specific actions affecting state changes. | <br> ## πŸ—‚οΈ Async Persist Storage To persist the global state using **Async Storage**, simply add the `asyncStorage` option: ```ts const useCount = createGlobalState(0, { asyncStorage: { key: "count", }, }); ``` ### πŸ”Ή How It Works βœ… **Automatically syncs the state with Async Storage** if the value is serializable. βœ… **Provides an `isAsyncStorageReady` flag** to indicate when the async storage has been reviewed and committed. βœ… **Uses `@react-native-async-storage/async-storage` by default** (make sure to install this package if needed). βœ… **Allows custom async storage managers** with `asyncStorageWrapper.addAsyncStorageManager(customAsyncStorageManager)`; Inside your components: ```tsx const [count, setCount, { isAsyncStorageReady }] = useCount(); ``` If you specify a key in `asyncStorage`, the state value persists automatically when serializable. When connecting to async storage, expect a **second render** that updates `isAsyncStorageReady`, indicating that the storage has been reviewed and the state is committed. ### πŸ”§ Custom Async Storage Manager You can configure your own storage selection by using `asyncStorageWrapper.addAsyncStorageManager`. Ensure that the manager is **added before any hook is called**. `index.ts` ```ts import { asyncStorageWrapper } from "react-global-state-hooks"; asyncStorageWrapper.addAsyncStorageManager(customAsyncStorageManager); ``` ## πŸ›  Creating a Global State Define a **global state** in **one line**: ```tsx import { createGlobalState } from "react-hooks-global-states/createGlobalState"; export const useCount = createGlobalState(0); ``` Now, use it inside a component: ```tsx const [count, setCount] = useCount(); return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>; ``` Works just like **useState**, but the **state is shared globally**! πŸŽ‰ --- ## 🎯 Selectors: Subscribing to Specific State Changes For **complex state objects**, you can subscribe to specific properties instead of the entire state: ```tsx export const useContacts = createGlobalState({ entities: [], selected: new Set<number>() }); ``` To access only the `entities` property: ```tsx const [contacts] = useContacts((state) => state.entities); return ( <ul> {contacts.map((contact) => ( <li key={contact.id}>{contact.name}</li> ))} </ul> ); ``` ### πŸ“Œ Using Dependencies in Selectors You can also add **dependencies** to a selector. This is useful when you want to derive state based on another piece of state (e.g., a filtered list). For example, if you're filtering contacts based on a `filter` value: ```tsx const [contacts] = useContacts( (state) => state.entities.filter((item) => item.name.includes(filter)), [filter] ); ``` Alternatively, you can pass dependencies inside an **options object**: ```tsx const [contacts] = useContacts((state) => state.entities.filter((item) => item.name.includes(filter)), { dependencies: [filter], isEqualRoot: (a, b) => a.entities === b.entities, }); ``` Unlike Redux, where only **root state changes trigger re-selection**, this approach ensures that **derived values recompute when dependencies change** while maintaining performance. --- ## πŸ”„ Reusing Selectors ### πŸ“Œ Creating a Selector ```tsx export const useContactsArray = useContacts.createSelectorHook((state) => state.entities); export const useContactsCount = useContactsArray.createSelectorHook((entities) => entities.length); ``` ### πŸ“Œ Using Selectors in Components ```tsx const [contacts] = useContactsArray(); const [count] = useContactsCount(); ``` #### βœ… Selectors support inline selectors and dependencies You can still **use dependencies** inside a selector hook: ```tsx const [filteredContacts] = useContactsArray( (contacts) => contacts.filter((c) => c.name.includes(filter)), [filter] ); ``` #### βœ… Selector hooks share the same state mutator The **stateMutator remains the same** across all derived selectors, meaning actions and setState functions stay consistent. ```tsx const [actions1] = useContactsArray(); const [actions2] = useContactsCount(); console.log(actions1 === actions2); // true ``` --- ## πŸŽ› State Actions: Controlling State Modifications Restrict **state modifications** by defining custom actions: ```tsx export const useContacts = createGlobalState( { filter: "", items: [] }, { actions: { async fetch() { return async ({ setState }) => { const items = await fetchItems(); setState({ items }); }; }, setFilter(filter: string) { return ({ setState }) => { setState((state) => ({ ...state, filter })); }; }, }, } ); ``` Now, instead of `setState`, the hook returns **actions**: ```tsx const [filter, { setFilter }] = useContacts(); ``` --- ## 🌍 Accessing Global State Outside Components Use `stateControls()` to **retrieve or update state outside React components**: ```tsx const [contactsRetriever, contactsApi] = useContacts.stateControls(); console.log(contactsRetriever()); // Retrieves the current state ``` #### βœ… Subscribe to changes ```tsx const unsubscribe = contactsRetriever((state) => { console.log("State updated:", state); }); ``` #### βœ… Subscriptions are great when one state depends on another. ```tsx const useSelectedContact = createGlobalState(null, { callbacks: { onInit: ({ setState, getState }) => { contactsRetriever( (state) => state.contacts, (contacts) => { if (!contacts.has(getState())) setState(null); } ); }, }, }); ``` --- ## 🎭 Using Context for Scoped State - **Scoped State** – Context state is **isolated inside the provider**. - **Same API** – Context supports **selectors, actions, and state controls**. ### πŸ“Œ Creating a Context ```tsx import { createContext } from "react-global-state-hooks/createContext"; export const [useCounterContext, CounterProvider] = createContext(0); ``` Wrap your app: ```tsx <CounterProvider> <MyComponent /> </CounterProvider> ``` Use the context state: ```tsx const [count] = useCounterContext(); ``` ### πŸ“Œ Context Selectors Works **just like global state**, but within the provider. --- ## πŸ”₯ Observables: Watching State Changes Observables **let you react to state changes** via subscriptions. ### πŸ“Œ Creating an Observable ```tsx export const useCounter = createGlobalState(0); export const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`); ``` ### πŸ“Œ Subscribing to an Observable ```tsx const unsubscribe = counterLogs((message) => { console.log(message); }); ``` ### πŸ“Œ Using Observables Inside Context ```tsx export const [useStateControls, useObservableBuilder] = useCounterContext.stateControls(); const createObservable = useObservableBuilder(); useEffect(() => { const unsubscribe = createObservable((count) => { console.log(`Updated count: ${count}`); }); return unsubscribe; }, []); ``` --- ## βš–οΈ `createGlobalState` vs. `createContext` | Feature | `createGlobalState` | `createContext` | | ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Scope** | Available globally across the entire app | Scoped to the Provider where it’s used | | **How to Use** | `const useCount = createGlobalState(0)` | `const [useCountContext, Provider] = createContext(0)` | | **createSelectorHook** | `useCount.createSelectorHook` | `useCountContext.createSelectorHook` | | **inline selectors?** | βœ… Supported | βœ… Supported | | **Custom Actions** | βœ… Supported | βœ… Supported | | **Observables** | `useCount.createObservable` | `const [, useObservableBuilder] = useCountContext.stateControls()` | | **State Controls** | `useCount.stateControls()` | `const [useStateControls] = useCountContext.stateControls()` | | **Best For** | Global app state (auth, settings, cache) | Scoped module state, reusable component state, or state shared between child components without being fully global | ## πŸ”„ Lifecycle Methods Global state hooks support lifecycle callbacks for additional control. ```tsx const useData = createGlobalState( { value: 1 }, { callbacks: { onInit: ({ setState }) => { console.log("Store initialized"); }, onStateChanged: ({ state, previousState }) => { console.log("State changed:", previousState, "β†’", state); }, computePreventStateChange: ({ state, previousState }) => { return state.value === previousState.value; }, }, } ); ``` Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates. ## Metadata There is a possibility to add non reactive information in the global state: ```tsx const useCount = createGlobalState(0, { metadata: { renders: 0 } }); ``` How to use it? ```tsx const [count, , metadata] = useCount(); metadata.renders += 1; ``` ## 🎯 Ready to Try It? πŸ“¦ **NPM Package:** [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states) πŸš€ Simplify your **global state management** in React & React Native today! πŸš€ # Using async persist storage ```ts const useCount = createGlobalState(0, { asyncStorage: { key: "count", }, }); ``` Inside your components ```tsx const [count, setCount, { isAsyncStorageReady }] = useCount(); ``` - if you specify a key into the `asyncStorage` this will persist the state value if the same is serializable - when connecting to the async storage you can expect a second render which will update isAsyncStorageReady indicating that the async storage was already reviewed and the state value is committed. The async storage default functionality depends on **@react-native-async-storage/async-storage** but this dependency is optional, install the package as a dependency if you want to enable persisted state. optionally you can configure your own selection for persisting storage by using `asyncStorageWrapper.addAsyncStorageManager`, notice that the manager should be added before any hook gets call;