React
import {
Provider,
useDispatch,
useQuery,
useSelector,
useStore,
useSuspenseQuery,
type QueryResult,
} from 'amos/react';
The React API is implemented by amos-react. It is a thin binding over an Amos store.
Provider
interface ProviderProps {
store: Store;
children: ReactNode;
}
const Provider: ({ store, children }: ProviderProps) => ReactElement;
Provides a store to hooks.
const store = createStore();
createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>,
);
Provider throws if store is missing.
useStore
function useStore(): Store;
Returns the nearest Amos store from context.
Throws if called outside <Provider />.
useDispatch
function useDispatch(): Dispatch;
Returns store.dispatch.
const dispatch = useDispatch();
return <button onClick={() => dispatch(countBox.add(1))}>+</button>;
useSelector
interface UseSelector extends Select {
(): Select;
}
const useSelector: UseSelector;
Selects state from the store and subscribes the component to changes.
const count = useSelector(countBox);
const [name, age] = useSelector([nameBox, ageBox]);
When called with no argument, it returns a select function:
const select = useSelector();
const visibleTodos = select(selectVisibleTodos());
const currentUser = select(currentUserBox);
During render, useSelector records every selected box or selector as a dependency. After store
updates, the component re-renders only when one of those dependencies changes according to Amos
selector equality rules.
For arrays, each element is tracked as an individual dependency.
useQuery
interface UseQuery {
<A extends any[] = any, R = any, S = any>(
action: SelectableAction<A, R, S>,
): [value: S, result: QueryResult<R>];
<A extends any[] = any, R = any>(
action: Action<A, R>,
): [value: Awaited<R> | undefined, result: QueryResult<R>];
}
const useQuery: UseQuery;
Dispatches an action and tracks its async status.
const [todos, result] = useQuery(loadTodos(userId));
if (result.isPending()) {
return <Spinner />;
}
if (result.isRejected()) {
return <ErrorView error={result.error} />;
}
return <TodoList todos={todos} />;
useQuery computes a cache key from the action and action conflictKey. It re-dispatches when the
key changes.
If the action has a bound selector via action(...).select(selectorOrBox), the first tuple value is
always the selected state. Otherwise, the first value is the fulfilled action result.
const loadTodos = action(async (dispatch, select, userId: number) => {
const todos = await api.getTodos(userId);
dispatch(todoMapBox.mergeAll(todos));
}).select(selectVisibleTodos);
QueryResult
class QueryResult<R> {
readonly status: 'pending' | 'fulfilled' | 'rejected';
readonly value: Awaited<R> | undefined;
readonly error: any;
isPending(): boolean;
isFulfilled(): boolean;
isRejected(): boolean;
toJSON(): QueryResultJSON<R>;
fromJS(state: JSONState<QueryResultJSON<R>>): this;
}
useQuery stores query results in an internal amos.queries box.
Pending query results are omitted from toJSON, so SSR snapshots do not serialize unfinished work.
useSuspenseQuery
interface UseSuspenseQuery {
<A extends any[] = any, R = any, S = any>(action: SelectableAction<A, R, S>): S;
<A extends any[] = any, R = any>(action: Action<A, R>): Awaited<R>;
}
const useSuspenseQuery: UseSuspenseQuery;
Suspense version of useQuery.
function Todos({ userId }: { userId: number }) {
const todos = useSuspenseQuery(loadTodos(userId));
return <TodoList todos={todos} />;
}
Behavior:
- If the query is pending, it throws the pending promise.
- If the query is rejected, it throws the error.
- If fulfilled, it returns the query value.
Use it inside React <Suspense> and an error boundary.