Incremental Complexity
1. Start with simple state
StateAdapt stores can be as simple as Svelte stores or RxJS BehaviorSubject
s:
const nameStore = adapt('Bob'); // 'name' is for Redux Devtools
const name$ = nameStore.state$;
<h2>Hello { $name$ }!</h2>
<button on:click={() => nameStore.set('Bilbo')}>Change Name</button>
Runes
Integration with runes coming soon.
2. Add selectors for derived state
Derived state defined in selectors can be moved outside of components without refactoring.
const nameStore = adapt('Bob', {
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const name$ = nameStore.state$;
const yelledName$ = nameStore.yelledName$;
<h2>Hello { $name$ }!</h2>
<h2>Hello { $yelledName$ }!</h2>
<button on:click={() => nameStore.set('Bilbo')}>Change Name</button>
3. Define state changes declaratively in stores
Maintain separation of concerns by keeping state logic together instead of scattered.
const nameStore = adapt('Bob', {
reverseName: name => name.split('').reverse().join(''),
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const yelledName$ = nameStore.yelledName$;
<h2>Hello { $yelledName$ }!</h2>
<button on:click={() => nameStore.set('Bilbo')}>Change Name</button>
<button on:click={() => nameStore.reverseName()}>Reverse Name</button>
4. Reuse state patterns with state adapters
If you need to reuse state logic, it's as simple as dragging it outside the adapt
call into a createAdapter
call.
const nameStore = adapt('Bob', {
const nameAdapter = createAdapter<string>()({
reverseName: name => name.split('').reverse().join(''),
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const yelledName$ = nameStore.yelledName$;
const nameStore1 = adapt('Bob', nameAdapter);
const yelledName1$ = nameStore1.yelledName$;
const nameStore2 = adapt('Bob', nameAdapter);
const yelledName2$ = nameStore2.yelledName$;
<h2>Hello { $yelledName1$ }!</h2>
<button on:click={() => nameStore1.set('Bilbo')}>Change Name</button>
<button on:click={() => nameStore1.reverseName()}>Reverse Name</button>
<h2>Hello { $yelledName2$ }!</h2>
<button on:click={() => nameStore2.set('Bilbo')}>Change Name</button>
<button on:click={() => nameStore2.reverseName()}>Reverse Name</button>
5. React to observable data sources
Multiple stores might need to react to the same observable, so it needs independent annotation.
const nameAdapter = createAdapter<string>()({
reverseName: name => name.split('').reverse().join(''),
concatName: (name, anotherName: string) => `${name} ${anotherName}`,
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const onNameFromServer = timer(3000).pipe(map(() => 'Joel'));
const nameStore1 = adapt('Bob', nameAdapter);
const nameStore1 = adapt('Bob', {
adapter: nameAdapter,
sources: onNameFromServer, // Set state
});
const yelledName1$ = nameStore1.yelledName$;
const nameStore2 = adapt('Bob', nameAdapter);
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
},
});
const yelledName2$ = nameStore2.yelledName$;
<h2>Hello { $yelledName1$ }!</h2>
<button (click)="name1Store.set('Bilbo')">Change Name</button>
<button (click)="name1Store.reverseName()">Reverse Name</button>
<h2>Hello { $yelledName2$ }!</h2>
<button (click)="name2Store.set('Bilbo')">Change Name</button>
<button (click)="name2Store.reverseName()">Reverse Name</button>
6. Share DOM event sources with multiple stores
Don't write callback functions to imperatively change state in multiple stores. Instead, declare the DOM event as an independent source that multiple stores can react to.
const nameAdapter = createAdapter<string>()({
reverseName: name => name.split('').reverse().join(''),
concatName: (name, anotherName: string) => `${name} ${anotherName}`,
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const onResetBoth = source();
const onNameFromServer = timer(3000).pipe(map(() => 'Joel'));
const nameStore1 = adapt('Bob', {
adapter: nameAdapter,
sources: onNameFromServer, // Set state
sources: {
set: onNameFromServer, // `set` is provided with all adapters
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const yelledName1$ = nameStore1.yelledName$;
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const yelledName2$ = nameStore2.yelledName$;
<h2>Hello { $yelledName1$ }!</h2>
<button (click)="name1Store.set('Bilbo')">Change Name</button>
<button (click)="name1Store.reverseName()">Reverse Name</button>
<h2>Hello { $yelledName2$ }!</h2>
<button (click)="name2Store.set('Bilbo')">Change Name</button>
<button (click)="name2Store.reverseName()">Reverse Name</button>
<button on:click={onResetBoth}>Reset Both</button>
7. Select state from multiple stores
joinStores
can define derived state from multiple stores that can be shared bewteen multiple components.
const nameAdapter = createAdapter<string>()({
reverseName: name => name.split('').reverse().join(''),
concatName: (name, anotherName: string) => `${name} ${anotherName}`,
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const onResetBoth = source();
const onNameFromServer = timer(3000).pipe(map(() => 'Joel'));
const nameStore1 = adapt('Bob', {
adapter: nameAdapter,
sources: {
set: onNameFromServer, // `set` is provided with all adapters
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const yelledName1$ = nameStore1.yelledName$;
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const yelledName2$ = nameStore2.yelledName$;
const bothBobs$ = joinStores({
name1: nameStore1,
name2: nameStore2,
})({
bothBobs: s => s.name1 === 'Bob' && s.name2 === 'Bob',
})().bothBobs$;
<h2>Hello { $yelledName1$ }!</h2>
<button (click)="name1Store.set('Bilbo')">Change Name</button>
<button (click)="name1Store.reverseName()">Reverse Name</button>
<h2>Hello { $yelledName2$ }!</h2>
<button (click)="name2Store.set('Bilbo')">Change Name</button>
<button (click)="name2Store.reverseName()">Reverse Name</button>
<button on:click={onResetBoth}>Reset Both</button>
{#if $bothBobs$} // [!code ++]
<h2>Hello Bobs!</h2>
{/if} // [!code ++]