### Install React Sweet State Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/README.md Install the library using npm or yarn. ```sh npm i react-sweet-state # or yarn add react-sweet-state ``` -------------------------------- ### Create a Store with Actions and Handlers Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/store.md Use `createStore` to initialize a store with state, actions, and lifecycle handlers. This example demonstrates loading todos on initialization and specifies a container for the store. ```javascript import { createStore } from 'react-sweet-state'; import { TodosContainer } from './container'; const actions = { load: () => async ({ setState }) => { const todos = await fetch('/todos'); setState({ todos }); }, }; const Store = createStore({ name: 'todos', initialState: { todos: [] }, actions, containedBy: TodosContainer, handlers: { onInit: () => async ({ dispatch }) => { await dispatch(actions.load()); }, }, }); ``` -------------------------------- ### Store with Container Handlers Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/container.md Define a store that uses a container and includes handlers for container updates. This example shows how to reset the state when the container updates. ```javascript const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: { increment: () => ({ setState }, { multiplier }) => { const currentCount = getState().count * multiplier; setState({ count: currentCount + 1 }); }, }, handlers: { onContainerUpdate: () => ({ setState }, { multiplier }) => { setState({ count: 0 }); // reset state on multiplier change }, }, }); const App = ({ n }) => ( {/* this starts from 10 */} ); ``` -------------------------------- ### Basic TypeScript Setup for React Sweet State Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/recipes/typescript.md Defines the initial state, actions, and creates a store with TypeScript generics. Most generics can be inferred by TypeScript. ```typescript import { createStore, createSubscriber, createHook, createContainer, Action, } from 'react-sweet-state'; type State = { count: number }; type Actions = typeof actions; const initialState: State = { count: 0, }; const actions = { increment: (by = 1): Action => ({ setState, getState }) => { setState({ count: getState().count + by, }); }, }; const CounterContainer = createContainer(); // Note: most times TS will be able to infer the generics const Store = createStore({ initialState, actions, containedBy: CounterContainer, }); const CounterSubscriber = createSubscriber(Store); const useCounter = createHook(Store); ``` -------------------------------- ### Basic React Sweet State Setup with Flow Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/recipes/flow.md Defines the initial state, actions, and creates a store using createStore, createSubscriber, and createHook. Ensure all necessary types are imported from 'react-sweet-state'. ```javascript import { createStore, createSubscriber, createHook, createContainer, type Action, type ContainerComponent, type HookFunction, type SubscriberComponent, } from 'react-sweet-state'; type State = { count: number }; type Actions = typeof actions; const initialState: State = { count: 0, }; const actions = { increment: (by = 1): Action => ({ setState, getState }) => { setState({ count: getState().count + by, }); }, }; const CounterContainer: ContainerComponent<{}> = createContainer(); const Store = createStore({ containedBy: CounterContainer, initialState, actions, }); const CounterSubscriber: SubscriberComponent = createSubscriber(Store); const useCounter: HookFunction = createHook(Store); ``` -------------------------------- ### Action Logging Example Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/devtools.md Actions defined with a `setState` call will be logged with their name and payload in Redux Devtools. Consider naming your stores for better organization. ```javascript const actions = { reset: () => ({ setState }) => { // will be logged as "reset" in redux devtools setState({ count: 0 }); }, }; ``` -------------------------------- ### Simple Action Example Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Define a simple action that conditionally calls setState based on the current state. This is useful for actions with minimal internal logic. ```javascript const actions = { reset: () => ({ setState, getState }) => { if (getState().count !== 0) { setState({ count: 0 }); } }, }; ``` -------------------------------- ### Composed Actions Example Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Demonstrates two actions where one triggers another. When testing, mock setState, getState, and dispatch to assert expected calls and arguments. ```javascript import { sendClickAnalytics } from './analytics'; const clickAnalytics = () => ({ setState, getState }) => { const currentTime = Date.now(); // Store last anaytics sent and execute setState({ lastAnalytics: currentTime }); sendClickAnalytics(getState()); }; const clickManager = () => ({ setState, getState, dispatch }) => { // Update click counter const currentCount = getState().count; setState({ count: currentCount + 1 }); // Send analytics every 5 clicks if (currentCount + 1 === 5) { dispatch(clickAnanlytics()); } }; const actions = { clickAnalytics, clickManager, }; ``` -------------------------------- ### Add a Logger Middleware Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/middlewares.md This example demonstrates how to create and add a simple logger middleware to `defaults.middlewares`. The middleware logs the payload of an action and the new state after the action is processed. ```javascript import { defaults } from 'react-sweet-state'; const logger = (storeState) => (next) => (arg) => { console.log(storeState.key, 'payload: ', arg); const result = next(arg); console.log(storeState.key, ':', storeState.getState()); return result; }; defaults.middlewares.add(logger); ``` -------------------------------- ### Asynchronous Action Example Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Define an asynchronous action that fetches user data, handling loading states and potential errors. This action uses async/await and interacts with an external API. ```javascript import { fetchUser } from './rest/user'; const actions = { fetchUserData: (userId) => async ({ setState, getState }) => { const currentUser = getState().user; // Return if data exists if (currentUser.data && currentUser.data.id === userId) { return; } // Set loading state to true setState({ user: { loading: true, data: null, error: null, }, }); try { // Initiate request const newUserData = await fetchStatus(userId); // Store success setState({ user: { loading: false, data: newUserData, error: null, }, }); } catch (e) { // Store failure setState({ loading: false, error: e.message, }); } }, }; ``` -------------------------------- ### Intertwined Actions Example Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Illustrates complex action composition. Avoid directly depending on transient state set by other actions, as this can indicate a poor separation of concerns. ```javascript // Example of intertwined actions const interimAction = () => ({ setState }) => { setState({ option2: Date.now() }); }; const initiatorAction = () => ({ setState, getState, dispatch }) => { setState({ option1: Date.now() }); dispatch(interimAction()); const { option2: newOption } = getState(); if (Date.now() - newOption < 50) { // fast action setState({ type: 'fast' }); } else { setState({ type: 'slow' }); } }; ``` -------------------------------- ### Enable Devtools Logging Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/devtools.md Set `defaults.devtools` to `true` to enable logging of actions and state changes in Redux Devtools. Ensure the Redux Devtools extension is installed. ```javascript import { defaults } from 'react-sweet-state'; defaults.devtools = true; ``` -------------------------------- ### Use Action in a React Component Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/basics/actions.md This example shows how to call an action from a React component using a custom hook. The `increment` action is invoked when a button is clicked. ```jsx import { useCounter } from './components/counter'; const App = () => { const [state, { increment }] = useCounter(); return ; }; ``` -------------------------------- ### Unit Testing Actions with Jest Source: https://context7.com/atlassian/react-sweet-state/llms.txt Unit test pure thunk actions by extracting the thunk and calling it directly with mocked dependencies. This example uses Jest for mocking and assertions. ```javascript import { fetchUser } from './api'; // The action under test const actions = { loadUser: (userId) => async ({ setState, getState }) => { if (getState().user?.id === userId) return; setState({ loading: true, error: null }); try { const user = await fetchUser(userId); setState({ user, loading: false }); } catch (e) { setState({ loading: false, error: e.message }); } }, }; // Test file jest.mock('./api', () => ({ fetchUser: jest.fn() })); describe('loadUser action', () => { const setState = jest.fn(); const getState = jest.fn(); const mockUser = { id: 'abc', name: 'Alice' }; beforeEach(() => jest.resetAllMocks()); it('skips fetch if user already loaded', async () => { getState.mockReturnValue({ user: { id: 'abc' } }); await actions.loadUser('abc')({ setState, getState }); expect(fetchUser).not.toHaveBeenCalled(); expect(setState).not.toHaveBeenCalled(); }); it('sets loading then stores user on success', async () => { getState.mockReturnValue({ user: null }); fetchUser.mockResolvedValue(mockUser); await actions.loadUser('abc')({ setState, getState }); expect(setState).toHaveBeenNthCalledWith(1, { loading: true, error: null }); expect(setState).toHaveBeenNthCalledWith(2, { user: mockUser, loading: false }); }); it('stores error message on failure', async () => { getState.mockReturnValue({ user: null }); fetchUser.mockRejectedValue(new Error('Network error')); await actions.loadUser('abc')({ setState, getState }); expect(setState).toHaveBeenNthCalledWith(2, { loading: false, error: 'Network error' }); }); }); ``` -------------------------------- ### Create a Basic Store Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/basics/store.md Define initial state and actions to create a store. The store instance is shared across all components using it. ```javascript import { createStore } from 'react-sweet-state'; // This is the value of the store on initialisation const initialState = { count: 0, }; // All the actions that mutate the store const actions = { increment: () => ({ setState }) => { // action code... }, }; const Store = createStore({ initialState, actions }); ``` -------------------------------- ### Define State and Actions with createStore Source: https://context7.com/atlassian/react-sweet-state/llms.txt Use `createStore` to define the initial state, actions, and optional lifecycle handlers for a store. It can be scoped using `createContainer`. The `onInit` handler is useful for initial data loading. ```javascript import { createStore, createContainer } from 'react-sweet-state'; export const TodosContainer = createContainer(); const actions = { load: () => async ({ setState }) => { const res = await fetch('/api/todos'); const todos = await res.json(); setState({ todos, loading: false }); }, add: (text) => ({ setState, getState }) => { setState({ todos: [...getState().todos, { id: Date.now(), text, done: false }] }); }, toggle: (id) => ({ setState, getState }) => { setState({ todos: getState().todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)), }); }, }; export const TodoStore = createStore({ name: 'todos', initialState: { todos: [], loading: true }, actions, containedBy: TodosContainer, handlers: { onInit: () => async ({ dispatch }) => { await dispatch(actions.load()); }, onDestroy: () => ({ setState }) => { setState({ todos: [], loading: true }); }, }, }); ``` -------------------------------- ### Create a Store with Advanced Configuration Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/basics/store.md Configure a store with a unique name, containment, and event handlers. Handlers can trigger actions on specific lifecycle events. ```javascript const Store = createStore({ initialState, actions, name: 'counter', containedBy: StoreContainer, handlers: { onInit: () => ({ setState }, containerProps) => {}, onUpdate: () => ({ setState }, containerProps) => {}, onDestroy: () => ({ setState }, containerProps) => {}, onContainerUpdate: () => ({ setState }, containerProps) => {}, }, }); ``` -------------------------------- ### createStore Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/store.md Creates a store instance with initial state, actions, and optional handlers and container configuration. ```APIDOC ## createStore ### Description Creates a store instance with initial state, actions, and optional handlers and container configuration. ### Method ```js createStore(config) ``` ### Parameters #### Arguments - **config** (Object) - Required - Configuration object for the store. - **initialState** (Object) - Required - The store's initial state value. - **actions** (Object) - Required - An object with all the actions that mutate the store instance. - **name** (string) - Optional - Useful for debugging and to generate more meaningful store keys. - **containedBy** (Container) - Optional - Specifies the Container component that should handle the store boundary. If set, RSS will throw an async uncaught error whenever the store is used without a container. - **handlers** (object) - Optional - Defines callbacks on specific events. - **onInit** (Function) - Action triggered on store initialisation. - **onUpdate** (Function) - Action triggered on store update. - **onDestroy** (Function) - Action triggered on store destroy. - **onContainerUpdate** (Function) - Action triggered when `containedBy` container props change, receives next/prev props as arguments. ### Returns (Object) - Used to create hooks, Subscribers and override Containers, related to the same store type. ### Example ```js import { createStore } from 'react-sweet-state'; import { TodosContainer } from './container'; const actions = { load: () => async ({ setState }) => { const todos = await fetch('/todos'); setState({ todos }); }, }; const Store = createStore({ name: 'todos', initialState: { todos: [] }, actions, containedBy: TodosContainer, handlers: { onInit: () => async ({ dispatch }) => { await dispatch(actions.load()); }, }, }); ``` ``` -------------------------------- ### Composing User and Project Actions Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/recipes/composition.md Demonstrates composing user and project actions using custom hooks. It shows how to trigger data loading based on user interactions and data changes, ensuring side effects are managed efficiently. ```javascript // ... import { useUserState } from './state-containers/user'; import { useProjectActions } from './state-containers/projects'; const UserProject = (uid) => { const [userState, userActions] = useUserState(); const projectActions = useProjectActions(); /* now we can useEffect to trigger userActions.load() and when user data is returned call projectActions.load(userState.data.id) */ useEffect(() => { // note: should be responsibility of the action to avoid multiple/useless fetch requests userActions.load(uid); }, [uid, userActions]); useEffect(() => { // this effect will be triggered every time user data changes if (userState.data) { projectActions.load(userState.data); } }, [userState.data, projectActions]); return; /* ... */ }; ``` -------------------------------- ### Create a Store and Hook Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/README.md Define a store with initial state and actions, then create a hook to access it. Use the hook in your React components to manage and interact with the store's state. ```javascript import { createStore, createHook } from 'react-sweet-state'; const Store = createStore({ // value of the store on initialisation initialState: { count: 0, }, // actions that trigger store mutation actions: { increment: (by = 1) => ({ setState, getState }) => { // mutate state synchronously setState({ count: getState().count + by, }); }, }, // optional, unique, mostly used for easy debugging name: 'counter', }); const useCounter = createHook(Store); const CounterApp = () => { const [state, actions] = useCounter(); return (

My counter

{state.count}
); }; ``` -------------------------------- ### Hydrate Store State with Container Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/recipes/rehydration.md Use a Container to pass initial state data and the `onInit` handler to populate the store. This approach ensures the initial state is available immediately, avoiding re-renders. ```javascript const CounterContainer = createContainer(); const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: {}, handlers: { onInit: () => ({ setState }, { initialCount }) => { setState({ count: initialCount }); }, }, }); const App = () => ( {/* ... */} ); ``` -------------------------------- ### createContainer(Store, [options]) Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/container.md This method allows creating a container by overriding store behavior, primarily recommended for testing and storybook purposes due to less flexibility and safety nets. ```APIDOC ## createContainer(Store, [options]) ### Description This API configuration provides less flexibility and safety nets than using Store's `containedBy` and `handlers`, so we recommend using this style mostly for testing/storybook purposes. ### Arguments #### Path Parameters - `Store` (Object) - Required - The store type returned from a call to `createStore` - `options` (Object) - Optional - containing one or more of the following keys: - `displayName` (string) - Optional - Used by React to better identify a component. Defaults to `Container(${storeName})`. - `onInit` (Function) - Optional - An action that will be triggered on container initialisation. It overrides store's `handlers.onInit` and will be triggered every time the container is mounted. - `onUpdate` (Function) - Optional - An action that will be triggered when props on a container change. It is different from store's `onUpdate` API. It overrides store's `handlers.onContainerUpdate` and does not receive current/prev props as arguments. - `onCleanup` (Function) - Optional - An action that will be triggered after the container has been unmounted and no more consumers of the store instance are present. Useful in case you want to clean up side effects like event listeners or timers. It overrides store's `handlers.onDestroy`. ### Example ```js import { createContainer } from 'react-sweet-state'; import { ColorsStore } from './colors'; const ColorsContainer = createContainer(ColorsStore, { onInit: () => ({ setState }, { initialColor }) => { setState({ color: initialColor }); }, }); it('should render with right color', () => { const mockColor = 'white'; const { asFragment } = render( ); expect(asFragment()).toMatchSnapshot(); }); ``` ``` -------------------------------- ### State Hydration for SSR Source: https://context7.com/atlassian/react-sweet-state/llms.txt Initialize store state synchronously from server-provided data using `handlers.onInit` within a container. This prevents extra re-renders on the client. ```javascript import { createStore, createContainer, createHook } from 'react-sweet-state'; const AppContainer = createContainer(); const Store = createStore({ containedBy: AppContainer, initialState: { user: null, preferences: {} }, actions: {}, handlers: { onInit: () => ({ setState }, { serverState }) => { if (serverState) setState(serverState); }, }, }); const useApp = createHook(Store); // Server-rendered HTML passes window.SERVER_STATE populated by the server const Root = () => ( ); ``` -------------------------------- ### Create a Container by Overriding Store Behavior Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/container.md Create a container by passing a store and options to createContainer. This is useful for testing or storybook purposes, allowing you to override initialization, update, and cleanup behaviors. ```javascript createContainer(Store, [options]); ``` ```javascript import { createContainer } from 'react-sweet-state'; import { ColorsStore } from './colors'; const ColorsContainer = createContainer(ColorsStore, { onInit: () => ({ setState }, { initialColor }) => { setState({ color: initialColor }); }, }); it('should render with right color', () => { const mockColor = 'white'; const { asFragment } = render( ); expect(asFragment()).toMatchSnapshot(); }); ``` -------------------------------- ### TypeScript Usage with Typed Stores and Hooks Source: https://context7.com/atlassian/react-sweet-state/llms.txt Demonstrates full TypeScript support in react-sweet-state, including typed actions, selectors, and containers. Ensure all necessary types are imported. ```typescript import { createStore, createContainer, createHook, createSubscriber, Action, } from 'react-sweet-state'; type Todo = { id: number; text: string; done: boolean }; type State = { todos: Todo[]; loading: boolean }; type ContainerProps = { userId: string }; type Actions = typeof actions; const actions = { load: (): Action => async ({ setState }, { userId }) => { setState({ loading: true }); const res = await fetch(`/api/users/${userId}/todos`); setState({ todos: await res.json(), loading: false }); }, toggle: (id: number): Action => ({ setState, getState }) => { setState({ todos: getState().todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)), }); }, }; export const TodoContainer = createContainer(); export const TodoStore = createStore({ initialState: { todos: [], loading: false }, actions, containedBy: TodoContainer, handlers: { onInit: (): Action => ({ dispatch }) => { dispatch(actions.load()); }, }, }); // Typed selector hook: returns Todo[], takes string argument export const useTodosByStatus = createHook(TodoStore, { selector: (state, status) => state.todos.filter((t) => (status === 'done' ? t.done : !t.done)), }); // Typed subscriber: SelectorState = number, no props export const TodoCountSubscriber = createSubscriber(TodoStore, { selector: (state) => state.todos.filter((t) => !t.done).length, }); // Usage const App = ({ userId }: { userId: string }) => ( {(remaining) =>

{remaining} tasks remaining

}
); ``` -------------------------------- ### Create a Subscriber Component Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/basics/subscriber.md Define a Store and then create a Subscriber component for it using `createSubscriber`. This component will be used to access the Store's state and actions. ```javascript import { createStore, createSubscriber } from 'react-sweet-state'; const Store = createStore({ initialState: { count: 0 }, actions: { increment: () => ({ setState, getState }) => { const currentCount = getState().count; setState({ count: currentCount + 1 }); }, }, }); export const CounterSubscriber = createSubscriber(Store); ``` -------------------------------- ### Using Counter Container with Different Scopes Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/container.md Demonstrates how to use the `CounterContainer` with different scoping options: `isGlobal` for a single global instance, `scope` for named global instances, and default local scoping. ```javascript const App = () => ( {/* this might be 1 */} {/* this might be 2 */} {/* this instance cannot be accessed elsewhere */} {/* this might be 20 */} ); ``` -------------------------------- ### Container Props Available in Actions Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/container.md Illustrates how props passed to a `Container` (like `multiplier`) are available within the `Store`'s actions, allowing for dynamic configuration. ```javascript const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: { increment: () => ({ setState }, { multiplier = 1 }) => { const currentCount = getState().count * multiplier; setState({ count: currentCount + 1 }); }, }, }); // ... const App = () => ( {/* this will be an odd number */} ); ``` -------------------------------- ### createContainer([options]) Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/container.md The recommended way to create containers. It allows passing them as the `containedBy` attribute on store creation, enabling child components to access store data and actions. ```APIDOC ## createContainer([options]) ### Description This is the recommended way of creating containers, passing them as `containedBy` attribute on store creation. ### Arguments #### Path Parameters - `options` (Object) - Optional - containing one or more of the following keys: - `displayName` (string) - Optional - Used by React to better identify a component. Defaults to `Container(${storeName})` ### Returns (Component) - This React component allows you to change the behaviour of child components by providing different Store instances or custom props to actions. It accepts the following props: - `isGlobal` (bool) - Optional - By default, Container defines a local store instance. This prop will allow child components to get data from the global store's registry instance instead. - `scope` (string) - Optional - This option will allow creating multiple global instances of the same store. Those instances will be automatically cleaned once all the containers pointing to the scoped version are removed from the tree. Changing a Container `scope` will: create a new Store instance, make `onInit` action run again and all child components will get the data from it. - `...props` (any) - Optional - Any additional prop set on the Container component is made available in the actions of child components. ### Example ```js // theming.js import { createContainer } from 'react-sweet-state'; export const ThemeContainer = createContainer(); // colors.js import { ThemeContainer } from './theming'; const ColorsStore = createStore({ // ... containedBy: ThemeContainer, }); // We can also have a FontSizesStore that has the same `containedBy` value // app.js const UserTheme = ({ colors, sizes }) => ( ); ``` ``` -------------------------------- ### Create a Custom Hook with createHook Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/basics/hook.md Define a custom hook by passing a store instance to `createHook`. This hook will provide access to the store's state and actions. ```javascript import { createStore, createHook } from 'react-sweet-state'; const Store = createStore({ initialState: { count: 0 }, actions: { increment: () => ({ setState, getState }) => { const currentCount = getState().count; setState({ count: currentCount + 1 }); }, }, }); export const useCounter = createHook(Store); ``` -------------------------------- ### Create a Container with Options Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/container.md Use createContainer to create a React component that wraps a store. This component can manage local or global store instances and pass custom props to actions. It's the recommended way to create containers. ```javascript createContainer([options]); ``` ```javascript // theming.js import { createContainer } from 'react-sweet-state'; export const ThemeContainer = createContainer(); // colors.js import { ThemeContainer } from './theming'; const ColorsStore = createStore({ // ... containedBy: ThemeContainer, }); // We can also have a FontSizesStore that has the same `containedBy` value // app.js const UserTheme = ({ colors, sizes }) => ( ); ``` -------------------------------- ### createSubscriber Source: https://context7.com/atlassian/react-sweet-state/llms.txt Creates a render-prop component equivalent to `createHook`. Still supported for compatibility. Accepts an optional `selector`. Renders children as a function with `(state, actions)`. ```APIDOC ## `createSubscriber` — Render-prop API (legacy) Creates a render-prop component equivalent to `createHook`. Still supported for compatibility. Accepts an optional `selector`. Renders children as a function with `(state, actions)`. ### Usage ```javascript import { createSubscriber } from 'react-sweet-state'; import { TodoStore } from './store'; const TodosByStatusSubscriber = createSubscriber(TodoStore, { selector: (state, props) => ({ todos: state.todos.filter((t) => t.done === (props.status === 'done')), loading: state.loading, }), }); const TodoSection = ({ status }) => ( {({ todos, loading }, actions) => loading ? (

Loading...

) : (
    {todos.map((todo) => (
  • {todo.text}
  • ))}
) }
); ``` ``` -------------------------------- ### TypeScript createHook with Selector and Props Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/recipes/typescript.md Shows how to define a hook or subscriber that accepts props. The fourth generic argument (`SelectorProps`) and the selector function signature must include these props. ```typescript type SelectorProps = { min: number }; type SelectorState = boolean; const selector = (state: State, props: SelectorProps): SelectorState => state.count > props.min; // this hook requires an argument const useCounter = createHook(Store, { selector }); // this component requires props const CounterSubscriber = createSubscriber(Store, { selector }); ``` -------------------------------- ### Testing a Simple Action Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Test a simple action by mocking its dependencies (setState, getState) and asserting that setState is called correctly or not at all. Ensure mocks are reset before each test. ```javascript import resetAction from './reset'; const thunk = resetAction(); const setState = jest.fn(); const getState = jest.fn(() => ({})); beforeEach(() => { jest.resetAllMocks(); }); it('should not setState if the count is 0', () => { getState.mockImplementation(() => ({ count: 0; })); thunk({ setState, getState }); expect(setState).not.toHaveBeenCalled(); }); it('should reset state if the current count is not 0', () => { getState.mockImplementation(() => { count: 1; }); thunk({ setState, getState }); expect(setState).toHaveBeenCalledTimes(1); expect(setState).toHaveBeenCalledWith({ count: 0 }); }); ``` -------------------------------- ### Create a Counter Container and Store Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/container.md Defines a `CounterContainer` and a `Store` that uses it. The `Store` has an `increment` action to modify its state. A `useCounter` hook is created to access the store's state and actions. ```javascript import { createStore, createContainer, createHook } from 'react-sweet-state'; export const CounterContainer = createContainer(); const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: { increment: () => ({ setState }) => { const currentCount = getState().count; setState({ count: currentCount + 1 }); }, }, }); const useCounter = createHook(Store); export const CounterButton = () => { const [{ count }, { increment }] = useCounter(); return ; }; ``` -------------------------------- ### createContainer Source: https://context7.com/atlassian/react-sweet-state/llms.txt Creates a Container component that controls Store instance boundaries. Without arguments, produces a standalone container. When passed a Store as first argument (legacy override mode), it supports `onInit`, `onUpdate`, and `onCleanup` lifecycle hooks directly. ```APIDOC ## `createContainer` — Scope and isolate Store instances Creates a Container component that controls Store instance boundaries. Without arguments, produces a standalone container. When passed a Store as first argument (legacy override mode), it supports `onInit`, `onUpdate`, and `onCleanup` lifecycle hooks directly. ### Usage ```javascript import { createStore, createContainer, createHook } from 'react-sweet-state'; export const CounterContainer = createContainer(); const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: { increment: () => ({ setState, getState }, { step = 1 }) => { setState({ count: getState().count + step }); }, }, handlers: { onInit: () => ({ setState }, { initialCount = 0 }) => { setState({ count: initialCount }); }, onContainerUpdate: () => ({ setState }, { initialCount }) => { if (initialCount !== undefined) setState({ count: initialCount }); }, }, }); const useCounter = createHook(Store); const CounterButton = () => { const [{ count }, { increment }] = useCounter(); return ; }; // Example of independent store instances: const App = () => ( <> {/* Global singleton instance */} {/* Scoped instance shared across two tree locations */} {/* Private local instance, cleaned up on unmount */} ); ``` ``` -------------------------------- ### Testing an Asynchronous Action Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/testing/actions.md Test an asynchronous action by mocking API calls and thunk dependencies. Use async/await in tests and assert on the sequence and content of setState calls for loading, success, and error states. ```javascript import { fetchUser } from './rest/user'; import fetchUserDataAction from './fetch-user-data'; jest.mock('./rest/user', () => ({ fetchUser: jest.fn(), })); const userId_1 = '12345-abc'; const mockUserData_1 = { id: userId, name: 'Place', surmane: 'Holder', avatar: 'https://placehold.it/80x80', }; const thunk = fetchUserDataAction(userId_1); const setState = jest.fn(); const getState = jest.fn(); beforeEach(() => { jest.resetAllMocks(); }); it('should return the existing user data if present', async () => { getState.mockImplementation(() => ({ user: mockState, })); await thunk({ setState, getState }); expect(setState).not.toHaveBeenCalled(); }); it('should setState to loading while request is in flight', async () => { fetchUser.mockImplementation(() => Promise.resolve(mockUserData_1)); await thunk({ setState, getState }); expect(setState).toHaveBeenNthCalledWith(1, { user: { loading: true, data: null, error: null, }, }); }); it('should store response data', async () => { fetchUser.mockImplementation(() => Promise.resolve(mockUserData_1)); await thunk({ setState, getState }); expect(setState).toHaveBeenNthCalledWith(2, { user: { loading: false, data: mockUserData_1, }, }); }); it('should store the error message on failure', async () => { const errorMessage = 'Failure message'; fetchUser.mockImplemantation(() => Promise.reject(errorMessage)); await thunk({ setState, getState }); expect(setState).toHaveBeenNthCalledWith(2, { user: { loading: false, error: errorMessage, }, }); }); ``` -------------------------------- ### createSubscriber Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/subscriber.md Creates a React component that subscribes to a store and re-renders when the store's state changes. It accepts a store and optional configuration options. ```APIDOC ## createSubscriber ### Description Creates a React component that subscribes to a store and re-renders when the store's state changes. It accepts a store and optional configuration options. ### Signature ```js createSubscriber(Store, [options]) ``` ### Parameters #### Arguments 1. `Store` _(Object)_: The store type returned by running `createStore`. 2. `[options]` _(Object)_: Optional configuration object. - `displayName` _(string)_: Used by React to better identify a component. Defaults to `Container(${storeName})`. - `selector` _(Function | null)_: A function triggered on state or props change. If `null`, the Subscriber will not re-render on state change. The selector function receives `state` and `props`. ### Returns _(Component)_: A React component that re-renders on store state change (unless a selector is defined). It accepts a function as a child (render prop) which is executed with `state` and `actions`. ### Example ```js import { createSubscriber } from 'react-sweet-state'; import Store from './store'; const TodosByStatusSubscriber = createSubscriber(Store, { selector: (state, props) => ({ todos: state.todos.filter((t) => t.status === props.status), }), }); const Todos = ({ status = 'done' }) => ( {({ todos }, actions) => todos.map((todo) => ) } ); ``` ``` -------------------------------- ### Create a custom hook with state and actions Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/hook.md Use `createHook` to generate a custom hook that returns both the selected state and all store actions. This hook triggers re-renders on state changes unless a selector is provided. ```javascript import { createHook } from 'react-sweet-state'; import Store from './store'; const useTodosByStatus = createHook(Store, { selector: (state, props) => state.todos.filter((t) => t.status === props.status), }); const Todos = ({ status = 'done' }) => { const [todos, actions] = useTodosByStatus({ status }); return todos.map((todo) => ); }; ``` -------------------------------- ### Async Action with State Updates Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/actions.md Implement asynchronous actions that can perform multiple state updates. Use `getState` to fetch the latest state between `setState` calls to avoid race conditions. ```javascript const actions = { load: () => async ({ setState, getState }, { api }) => { if (getState().loading === true) return; setState({ loading: true, }); const todos = await api.get('/todos'); setState({ loading: false, data: todos, }); }, }; ``` -------------------------------- ### Create a Hook with a State Selector Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/selector.md Use a selector function to extract and memoize a specific part of the store's state. The hook re-renders only when the selected state changes. ```javascript import { createStore, createHook } from 'react-sweet-state'; const Store = createStore({ initialState: { currentUserId: null, users: [], }, actions: { // fetch users, set currentUserId, ecc... }, }); const getCurrentUser = (state) => state.users.find((user) => user.id === state.currentUserId); export const useCurrentUser = createHook(Store, { selector: getCurrentUser, }); ``` -------------------------------- ### Use Legacy Render-Prop API with `createSubscriber` Source: https://context7.com/atlassian/react-sweet-state/llms.txt The `createSubscriber` function provides a render-prop component equivalent to `createHook`, maintained for compatibility. It accepts an optional `selector` and renders children as a function receiving `state` and `actions`. ```javascript import { createSubscriber } from 'react-sweet-state'; import { TodoStore } from './store'; const TodosByStatusSubscriber = createSubscriber(TodoStore, { selector: (state, props) => ({ todos: state.todos.filter((t) => t.done === (props.status === 'done')), loading: state.loading, }), }); const TodoSection = ({ status }) => ( {({ todos, loading }, actions) => loading ? (

Loading...

) : (
    {todos.map((todo) => (
  • {todo.text}
  • ))}
) }
); ``` -------------------------------- ### Create and Consume Stores with Hooks Source: https://github.com/atlassian/react-sweet-state/blob/master/README.md Defines a store for a counter with increment action and a hook to consume it. Use this pattern to create and manage shared state in your React application. ```js import { createStore, createHook } from 'react-sweet-state'; const Store = createStore({ // value of the store on initialisation initialState: { count: 0, }, // actions that trigger store mutation actions: { increment: () => ({ setState, getState }) => { // mutate state synchronously setState({ count: getState().count + 1, }); }, }, // optional, unique, mostly used for easy debugging name: 'counter', }); const useCounter = createHook(Store); ``` ```js // app.js import { useCounter } from './components/counter'; const CounterApp = () => { const [state, actions] = useCounter(); return (

My counter

{state.count}
); }; ``` -------------------------------- ### getState Method Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/actions.md The `getState` method returns the current state of the store. Be mindful of stale state during asynchronous operations. ```APIDOC ##### - getState This method returns a fresh state every time it is called: ```js // inside your action if (getState().loading) { /* ... */ } ``` Note: Pay attention while storing parts of the state in a variable inside the actions: they might become stale especially during async operations. ``` -------------------------------- ### Create Scoped Store Instances with `createContainer` Source: https://context7.com/atlassian/react-sweet-state/llms.txt Utilize `createContainer` to create a Container component that manages store instance boundaries. This allows for standalone, scoped, or globally shared store instances. Lifecycle hooks like `onInit`, `onUpdate`, and `onCleanup` can be configured. ```javascript import { createStore, createContainer, createHook } from 'react-sweet-state'; export const CounterContainer = createContainer(); const Store = createStore({ containedBy: CounterContainer, initialState: { count: 0 }, actions: { increment: () => ({ setState, getState }, { step = 1 }) => { setState({ count: getState().count + step }); }, }, handlers: { onInit: () => ({ setState }, { initialCount = 0 }) => { setState({ count: initialCount }); }, onContainerUpdate: () => ({ setState }, { initialCount }) => { if (initialCount !== undefined) setState({ count: initialCount }); }, }, }); const useCounter = createHook(Store); const CounterButton = () => { const [{ count }, { increment }] = useCounter(); return ; }; // Three separate, fully independent store instances const App = () => ( <> {/* Global singleton instance */} {/* Scoped instance shared across two tree locations */} {/* Private local instance, cleaned up on unmount */} ); ``` -------------------------------- ### dispatch Method Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/api/actions.md The `dispatch` method allows you to trigger other actions from within an action. This is useful for abstracting functionality and creating private actions. ```APIDOC ##### - dispatch This method allows you to trigger other actions from the explicitly called action. It can also be used to abstract away some generic functionality or to create private actions that are not exposed to consumers. ```js const actions = { increment: (n) => ({ setState, getState }) => { setState({ count: getState().count + n }); }, resetTo: (n) => ({ setState, dispatch }) => { setState({ count: 0 }); dispatch(actions.increment(n)); }, }; ``` ``` -------------------------------- ### Private Actions with Dispatch Source: https://github.com/atlassian/react-sweet-state/blob/master/docs/advanced/actions.md Organize reusable logic into private actions that can be dispatched by other actions. This promotes a cleaner and more modular action structure. ```javascript const setLoading = () => ({ setState }) => { setState({ loading: true }); }; const setData = (data) => ({ setState }) => { setState({ loading: false, data }); }; const actions = { load: () => async ({ getState, dispatch }, { api }) => { if (getState().loading === true) return; dispatch(setLoading()); const todos = await api.get('/todos'); dispatch(setData(todos)); }, }; ```