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$} // [!code ++]
   <h2>Hello Bobs!</h2>
 {/if} // [!code ++]

StackBlitz