teaful
repository·master·Indexed 20 days ago
https://github.com/teafuljs/teafulA 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.
What's inside teaful
- Teaful is a tiny, easy, and powerful React state management library designed for simplicity and developer experience. It is optimized for minimal bundle size and high performance.
Register events after a store update
masterYou 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:
- Permanent events: Defined inside
createStore. These run for every change made to the store throughout its lifecycle. - Temporal events: Defined inside
useStoreorwithStore. These run only while the component is mounted and are automatically removed when the component unmounts.
Note: When using permanent events, you can use
getStoreto 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"); } } // ... }- Permanent events: Defined inside
Key advantages of using Teaful
masterTeaful 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.
Use the onAfterUpdate listener to monitor store changes
masterThe
onAfterUpdatelistener 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 currentstoreand theprevStore(the state before the update).function onAfterUpdate({ store, prevStore }) { // Logic to run after store updates }How to export your store correctly
masterWhen defining your store in a file, you must export the returned methods (like
useStore,getStore, orwithStore) as constants to ensure they can be imported correctly by other components.Recommended: Named Exports
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();Install teaful via yarn
masterTo use Teaful in your project, install the package using yarn:
yarn add teafulUpdate multiple properties without triggering full store rerenders
masterUpdating 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 viasetStore[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: "" });Use multiple stores in Teaful
masterYou can manage multiple independent state containers by calling
createStoremultiple times. Each call tocreateStorereturns a unique set of hooks. To keep your code organized, it is a common pattern to destructure the returneduseStoreand rename it to something descriptive (e.g.,useCartoruseCounter) 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 });Modify pages and API routes in Next.js
masterThis project follows the Next.js Pages Router convention:
- React Pages: Edit
pages/index.tsxto modify the main landing page. Changes will trigger an auto-update in the browser. - API Routes: Files located in the
pages/apidirectory are treated as API endpoints rather than React pages. For example,pages/api/hello.tsis accessible at/api/hello.
- React Pages: Edit
Add a new store property on the fly
masterYou can create new properties in the store dynamically using
useStore,getStore, orwithStore, even if those properties were not defined in the initialcreateStoreconfiguration.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 }Use multiple independent stores
masterTo maintain separation of concerns, you can create multiple independent stores by calling
createStoremultiple 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(); // ... }Recommended way to export a store
masterTo 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
useStoredirectly.// ✅ 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'