### 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));
},
};
```