teaful

repository·master·Indexed 20 days ago

https://github.com/teafuljs/teaful

A tiny (<1kb) and powerful state management library for React and Preact. Teaful focuses on simplicity by eliminating the need for actions, reducers, or providers, providing high performance through granular re-renders. It includes the createStore function to initialize state and provides proxy helpers such as useStore for hooks, setStore for external modifications, getStore for non-subscription access, and withStore for class components.

Tokens
13.9K
Snippets
42
Records
47
Agent score
71%

What's inside teaful

  1. Register events after a store update

    master

    You can register events that execute after a store update. This is useful for validation, error handling, or optimistic updates. There are two types of registration:

    1. Permanent events: Defined inside createStore. These run for every change made to the store throughout its lifecycle.
    2. Temporal events: Defined inside useStore or withStore. These run only while the component is mounted and are automatically removed when the component unmounts.

    Note: When using permanent events, you can use getStore to trigger updates from within the event handler.

    // Permanent event
    export const { useStore, getStore } = createStore(
      initialStore,
      onAfterUpdate
    );
    
    function onAfterUpdate({ store, prevStore }) {
      if (store.count > 99 && !store.errorMsg) {
        const [, setErrorMsg] = getStore.errorMsg();
        setErrorMsg("The count value should be lower than 100");
        return;
      }
    }
    
    // Temporal event
    function Count() {
      const [count, setCount] = useStore.count(0, onAfterUpdate);
      const [errorMsg, setErrorMsg] = useStore.errorMsg();
    
      function onAfterUpdate({ store, prevStore }) {
        // This event lasts as long as this component lives
        if (store.count > 99 && !store.errorMsg) {
          setErrorMsg("The count value should be lower than 100");
        }
      }
      // ...
    }
  2. Key advantages of using Teaful

    master

    Teaful provides several benefits for React applications:

    • Tiny: Adds less than 1Kb to your bundle.
    • Easy: Simple API and painless setup.
    • Boilerplate Free: Eliminates the need for actions, reducers, middleware, selectors, or providers.
    • Performant: Implements optimized rendering where components only re-render when the expected property changes.
    • Tooling: Offers first-class TypeScript support and dedicated devtools.
  3. Use the onAfterUpdate listener to monitor store changes

    master

    The onAfterUpdate listener allows you to react to changes in the store. It is useful for tasks such as validating properties, managing error messages, performing optimistic updates, or synchronizing side effects. The listener receives an object containing the current store and the prevStore (the state before the update).

    function onAfterUpdate({ store, prevStore }) {
      // Logic to run after store updates
    }
  4. How to export your store correctly

    master

    When defining your store in a file, you must export the returned methods (like useStore, getStore, or withStore) as constants to ensure they can be imported correctly by other components.

    Export the specific methods you need as constants:

    export const { useStore, getStore, withStore } = createStore();

    Alternative: Default Export of a specific method

    You can also export a single method as a default export:

    const { useStore } = createStore();
    export default useStore;

    🚫 Avoid: Default Export of the createStore call

    Do not do the following, as it will prevent named imports from working:

    export default createStore();
  5. Update multiple properties without triggering full store rerenders

    master

    Updating the entire store object via setStore({ ...store, key: value }) causes all components observing any part of the store to rerender. To update multiple properties efficiently without disturbing components observing unrelated parts of the store, use a helper that calls individual property updaters via setStore[key](value).

    Avoid this (causes full rerender): setStore({ ...store, count: 10, username: "" });

    Do this (fragmented update): Create a helper that iterates through fields and calls the specific updater for each.

    export const { useStore, setStore } = createStore(initialStore);
    
    // Helper to update multiple fields individually
    export function setFragmentedStore(fields) {
      Object.entries(fields).forEach(([key, value]) => {
        setStore[key](value);
      });
    }
    
    // Usage
    setFragmentedStore({ count: 10, username: "" });
  6. Use multiple stores in Teaful

    master

    You can manage multiple independent state containers by calling createStore multiple times. Each call to createStore returns a unique set of hooks. To keep your code organized, it is a common pattern to destructure the returned useStore and rename it to something descriptive (e.g., useCart or useCounter) when exporting from a central store file.

    import createStore from "teaful";
    
    // Create and rename hooks for different stores
    export const { useStore: useCart } = createStore({ price: 0, items: [] });
    export const { useStore: useCounter } = createStore({ count: 0 });
  7. Modify pages and API routes in Next.js

    master

    This project follows the Next.js Pages Router convention:

    • React Pages: Edit pages/index.tsx to modify the main landing page. Changes will trigger an auto-update in the browser.
    • API Routes: Files located in the pages/api directory are treated as API endpoints rather than React pages. For example, pages/api/hello.ts is accessible at /api/hello.
  8. Add a new store property on the fly

    master

    You can create new properties in the store dynamically using useStore, getStore, or withStore, even if those properties were not defined in the initial createStore configuration.

    When using the hook pattern, you can provide an initial value to the property to ensure it is initialized immediately. If you do not provide an initial value, the property will be created as undefined (or its natural default) and can be populated later using an updater function.

    const { useStore } = createStore({ username: "Aral" });
    
    function CreateProperty() {
      // Creates 'cart.price' with an initial value of 0
      const [price, setPrice] = useStore.cart.price(0);
    
      return <div>Price: {price}</div>;
    }
    
    function OtherComponent() {
      // The store is now automatically updated to include the new property:
      // { username: 'Aral', cart: { price: 0 } }
      const [store] = useStore();
      console.log(store.cart.price); // 0
    }
  9. Use multiple independent stores

    master

    To maintain separation of concerns, you can create multiple independent stores by calling createStore multiple times. Each call returns a unique set of hooks and helpers.

    // store.js
    import createStore from "teaful";
    
    export const { useStore: useCart } = createStore({ price: 0, items: [] });
    export const { useStore: useCounter } = createStore({ count: 0 });
    
    // Component.js
    import { useCart } from "./store";
    
    export default function Cart() {
      const [price, setPrice] = useCart.price();
      // ...
    }
  10. Recommended way to export a store

    master

    To ensure proxies work correctly when importing, use named exports for the store helpers instead of a default export. This allows you to import specific helpers like useStore directly.

    // ✅ Recommended: Named exports
    export const { useStore, getStore, withStore } = createStore({
      cart: { price: 0, items: [] },
    });
    
    // In another file:
    import { useStore } from '../store'
    
    // ❌ Avoid: Default export
    export default createStore({ cart: { price: 0, items: [] } });
    
    // This will not work well with proxies:
    import { useStore } from '../store'