import { InitializedSmartStore, AdaptOptions, NotAdaptOptions, InitialState } from '@state-adapt/rxjs';
import { ReactionsWithSelectors, Selectors } from '@state-adapt/core';
import { ProxyStoreTuple } from './proxy-store-tuple.type';
/**
`useAdapt` is a hook that wraps {@link StateAdapt.adapt} and {@link useStore}. It creates a store, immediately subscribes to it,
and returns a tuple `[selectorResults, setState]` where `selectorResults` is a proxy object containing results from the store's selectors,
and `setState` is a function with additional properties assigned from the store created by {@link StateAdapt.adapt}.
`useAdapt` is like an advanced version of [`useState`](https://beta.reactjs.org/reference/react/useState)
or [`useReducer`](https://beta.reactjs.org/reference/react/useReducer). All of the values you pass into it
are only used once, when the store is created. Any further updates to the store need to be done through
the store itself (returned by `useAdapt` in the second position of the tuple) or indirectly through the sources
passed into `useAdapt`.
### Example: initialState only
`useAdapt(initialState)`
`useAdapt` starts with very similar syntax to `useState`'s. The main difference is that `state` is accessed
as a property of the first tuple element. Also, the `setState` function has a `reset` property function that resets the store's state.
```tsx
import { useAdapt } from '@state-adapt/react';
export function MyComponent() {
const [name, setName] = useAdapt('John');
// Shows "John" first
// Shows "Johnsh" when the "Set" button is clicked
// Shows "John" again when the "Reset" button is clicked
return (
<>
{name.state}
>
);
}
```
### Example: Initial state factory
`useAdapt(() => initialState)`
Just like `useState`, you can pass a function that returns the initial state. The store calls it when it
activates, keeps that value for as long as it stays active, and discards it when it deactivates — so the
factory runs again for each activation, but not for re-renders.
This helps when initial state might be different at each time the store is being used, like with `localStorage`:
```tsx
import { useAdapt } from '@state-adapt/react';
export function MyComponent() {
// Each mount reads `localStorage`, and `reset` goes back to what it read
const [name, setName] = useAdapt(() => localStorage.getItem('name') ?? 'John');
return (
<>
{name.state}
>
);
}
```
A one-off read of initial state will not use a cached value, but call the state factory function.
### Example: Using an adapter
`useAdapt(initialState, adapter)`
You can also pass in a state {@link Adapter} object to customize the state change functions and selectors.
```tsx
import { useAdapt } from '@state-adapt/react';
export function MyComponent() {
const [name, setName] = useAdapt('John', {
concat: (state, payload: string) => state + payload,
selectors: {
length: state => state.length,
},
});
// Shows 'John' and 4 first
// Shows 'Johnsh' and 6 when the "Concat" button is clicked
// Shows 'John' and 4 again when the "Reset" button is clicked
return (
<>
{name.state}
{name.length}
>
);
}
```
### Example: Using {@link AdaptOptions}
`useAdapt(initialState, { adapter, sources, path })`
You can also define an adapter, sources, and/or a state path as part of an {@link AdaptOptions} object.
Sources allow the store to declaratively react to external events rather than being commanded
by imperative callback functions.
```tsx
import { useAdapt } from '@state-adapt/react';
import { interval } from 'rxjs';
const onTick = interval(1000);
export function MyComponent() {
const [clock] = useAdapt(0, {
adapter: {
increment: state => state + 1,
},
sources: onTick, // or [onTick], or { set: onTick }, or { set: [onTick] }
path: 'clock',
});
// Shows 0, 1, 2, 3, etc. every second
return
{clock.state}
;
}
```
When a store is subscribed to, it passes the subscriptions up to its sources.
For example, if a store has an HTTP source, it will be triggered when the store
receives its first subscriber, and it will be canceled when the store loses its
last subscriber. `useAdapt` immediately subscribes.
There are 4 possible ways sources can be defined:
1\. A source can be a single source or [Observable](https://rxjs.dev/guide/observable)<`State`>. When the source emits, it triggers the store's `set` method
with the payload.
#### Example: Single source or observable
```tsx
import { useAdapt } from '@state-adapt/react';
import { source } from '@state-adapt/rxjs';
const onNameChange = source();
export function MyComponent() {
const [name] = useAdapt('John', {
sources: onNameChange,
path: 'name',
});
// Shows 'John' first
// Shows 'Johnsh' when the "Set" button is clicked
return (
<>
{name.state}
>
);
}
```
2\. A source can be an array of sources or [Observable](https://rxjs.dev/guide/observable)<`State`>. When any of the sources emit, it triggers the store's `set`
method with the payload.
#### Example: Array of sources or observables
```tsx
import { useAdapt } from '@state-adapt/react';
import { source } from '@state-adapt/rxjs';
const onNameChange = source();
const onNameChange2 = source();
export function MyComponent() {
const [name] = useAdapt('John', {
sources: [onNameChange, onNameChange2],
path: 'name',
});
// Shows 'John' first
// Shows 'Johnsh' when the "Set" button is clicked
// Shows 'Johnsh2' when the "Set2" button is clicked
return (
<>
{name.state}
>
);
}
```
3\. A source can be an object with keys that match the names of the {@link Adapter} state change functions, with a corresponding source or array of
sources that trigger the store's reaction with the payload.
#### Example: Object of sources or observables
```tsx
import { useAdapt } from '@state-adapt/react';
import { source } from '@state-adapt/rxjs';
const onNameChange = source();
const onNameReset = source();
export function MyComponent() {
const [name] = useAdapt('John', {
sources: {
set: onNameChange,
reset: [onNameReset], // Can be array of sources too
},
path: 'name',
});
// Shows 'John' first
// Shows 'Johnsh' when the "Set" button is clicked
// Shows 'John' again when the "Reset" button is clicked
return (
<>
{name.state}
>
);
}
```
4\. A source can be a function that takes in a detached store (doesn't chain off of sources) and returns any of the above
types of sources or observables.
#### Example: Function that returns an observable
```tsx
import { useAdapt } from '@state-adapt/react';
import { toSource } from '@state-adapt/rxjs';
import { delay, map } from 'rxjs/operators';
export function MyComponent() {
const [name] = useAdapt('John', {
sources: store => store.state$.pipe(
delay(1000),
map(name => `${name}sh`),
toSource('recursive onNameChange'),
),
});
// Shows 'John' first
// Shows 'Johnsh' after 1 second, then 'Johnshsh' after 1 more second, etc.
return
{name.state}
;
}
```
Defining a path alongside sources is recommended to enable easier debugging with Redux DevTools. It's easy to trace state changes
caused by user events, but it's much harder to trace state changes caused by spontaneous RxJS streams.
The path specifies the location in the global store you will find the state for the store
(while it is being used). StateAdapt splits this string at periods `'.'` to create an object path within
the global store. Here are some example paths and the resulting global state objects:
#### Example: Paths and global state
```tsx
import { useAdapt } from '@state-adapt/react';
export function MyComponent() {
const [count1] = useAdapt(0, { path: 'count.1' });
const [count2] = useAdapt(0, { path: 'count.2' });
// global state:
// {
// count: {
// 1: 0,
// 2: 0,
// }
// }
return
{count1.state}, {count2.state}
;
}
```
Each store completely owns its own state. If more than one store tries to use the same path, StateAdapt will throw this error:
`Path '${path}' collides with '${existingPath}', which has already been initialized as a state path.`
This applies both to paths that are identical as well as paths that are subtrings of each other. For example, if `'featureA'`
is already being used by a store and then another store tried to initialize at `'featureA.number'`, that error would be thrown.
To help avoid this error, StateAdapt provides a {@link getId} function that can be used to generate unique paths:
#### Example: getId for unique paths
```tsx
import { getId } from '@state-adapt/core';
import { useAdapt } from '@state-adapt/react';
const path0 = 'number' + getId();
const path1 = 'number' + getId();
export function MyComponent() {
const [states1, store1] = useAdapt(0, { path: path0 });
const [states2, store2] = useAdapt(0, { path: path1 });
// global state: { number0: 0, number1: 0 }
return
{states1.state} {states2.state}
;
}
```
### No path
If no path is provided, then the store's path defaults to the result of calling {@link getId}.
### Remember!
Stores need to have subscribers in order to activate and subscribe to their sources.
*/
export declare function useAdapt, R extends ReactionsWithSelectors, ReturnedSources = unknown>(initialState: InitialState, second?: (R & {
selectors?: S;
} & NotAdaptOptions) | AdaptOptions): ProxyStoreTuple>;