Skip to content

Incremental Complexity ​

1. Start with simple state ​

StateAdapt stores can be as simple as Svelte stores or RxJS BehaviorSubjects:

tsx
const nameStore = adapt('Bob'); // 'name' is for Redux Devtools
const name$ = nameStore.state$;
svelte
<h2>Hello { $name$ }!</h2>
<button on:click={() => nameStore.set('Bilbo')}>Change Name</button>

StackBlitz

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.

typescript
const nameStore = adapt('Bob', {
  selectors: {
    yelledName: name => name.toUpperCase(), // Will be memoized
  },
});
const name$ = nameStore.state$;
const yelledName$ = nameStore.yelledName$;
svelte
<h2>Hello { $name$ }!</h2>
<h2>Hello { $yelledName$ }!</h2>
<button on:click={() => nameStore.set('Bilbo')}>Change Name</button>

StackBlitz

3. Define state changes declaratively in stores ​

Maintain separation of concerns by keeping state logic together instead of scattered.

typescript
const nameStore = adapt('Bob', {
  reverseName: name => name.split('').reverse().join(''), 
  selectors: {
    yelledName: name => name.toUpperCase(), // Will be memoized
  },
});
const yelledName$ = nameStore.yelledName$;
svelte
 <h2>Hello { $yelledName$ }!</h2>
 <button on:click={() => nameStore.set('Bilbo')}>Change Name</button>
 <button on:click={() => nameStore.reverseName()}>Reverse Name</button>

StackBlitz

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.

typescript
  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$;
svelte
 <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>

StackBlitz

5. React to observable data sources ​

Multiple stores might need to react to the same observable, so it needs independent annotation.

typescript
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$;
svelte
 <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>

StackBlitz

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.

typescript
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$;
svelte
 <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>

StackBlitz

7. Select state from multiple stores ​

joinStores can define derived state from multiple stores that can be shared bewteen multiple components.

typescript
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$;
svelte
 <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$}
   <h2>Hello Bobs!</h2>
 {/if}

StackBlitz