1. Start with simple state
StateAdapt stores can be as simple as createSignal
or RxJS BehaviorSubject
s:
tsx
function SimpleState() {
const nameStore = adapt('Bob');
const name = fromAdapt(nameStore);
return (
<>
<h2>Hello {name.state()}!</h2>
<button onClick={() => nameStore.set('Bilbo')}>Change Name</button>
</>
);
}
2. Add selectors for derived state
tsx
function DerivedState() {
const nameStore = adapt('Bob', {
selectors: {
yelledName: name => name.toUpperCase(), // Will be memoized
},
});
const name = fromAdapt(nameStore);
return (
<>
<h2>Hello {name.state()}!</h2>
<h2>Hello {name.yelledName()}!</h2>
<button onClick={() => 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.
tsx
function StateChanges() {
const nameStore = adapt('Bob', {
reverseName: (name) => name.split('').reverse().join(''),
selectors: {
yelledName: (name) => name.toUpperCase(), // Will be memoized
},
});
const name = fromAdapt(nameStore);
return (
<>
<h2>Hello {name.yelledName()}!</h2>
<button onClick={() => nameStore.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore.reverseName()}>Reverse Name</button>
</>
);
}
4. Reuse state patterns with state adapters
tsx
const nameAdapter = createAdapter<string>()({
reverseName: (name) => name.split('').reverse().join(''),
selectors: {
yelledName: (name) => name.toUpperCase(), // Will be memoized
},
});
function StateAdapters() {
const nameStore1 = adapt('Bob', nameAdapter);
const name1 = fromAdapt(nameStore1);
const nameStore2 = adapt('Bob', nameAdapter);
const name2 = fromAdapt(nameStore2);
return (
<>
<h2>Hello {name1.yelledName()}!</h2>
<button onClick={() => nameStore1.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore1.reverseName()}>Reverse Name</button>
<h2>Hello {name2.yelledName()}!</h2>
<button onClick={() => nameStore2.set('Bilbo')}>Change Name</button>
<button onClick={() => 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.
tsx
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'));
function ObservableSources() {
const nameStore1 = adapt('Bob', nameAdapter);
const nameStore1 = adapt('Bob', {
adapter: nameAdapter,
sources: onNameFromServer, // Set state
});
const name1 = fromAdapt(nameStore1);
const nameStore2 = adapt('Bob', nameAdapter);
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
},
});
const name2 = fromAdapt(nameStore2);
return (
<>
<h2>Hello {name1.yelledName()}!</h2>
<button onClick={() => nameStore1.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore1.reverseName()}>Reverse Name</button>
<h2>Hello {name2.yelledName()}!</h2>
<button onClick={() => nameStore2.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore2.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.
tsx
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'));
function DomSources() {
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 name1 = fromAdapt(nameStore1);
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const name2 = fromAdapt(nameStore2);
return (
<>
<h2>Hello {name1.yelledName()}!</h2>
<button onClick={() => nameStore1.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore1.reverseName()}>Reverse Name</button>
<h2>Hello {name2.yelledName()}!</h2>
<button onClick={() => nameStore2.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore2.reverseName()}>Reverse Name</button>
<button onClick={onResetBoth}>Reset Both</button>
</>
);
}
7. Select state from multiple stores
tsx
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'));
function MultiStoreSelectors() {
const nameStore1 = adapt('Bob', {
adapter: nameAdapter,
sources: {
set: onNameFromServer, // `set` is provided with all adapters
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const name1 = fromAdapt(nameStore1);
const nameStore2 = adapt('Bob', {
adapter: nameAdapter,
sources: {
concatName: onNameFromServer, // Trigger a specific state reaction
reset: onResetBoth, // `reset` is provided with all adapters
},
});
const name2 = fromAdapt(nameStore2);
const bothBobs = () => name1.state() === 'Bob' && name2.state() === 'Bob';
return (
<>
<h2>Hello {name1.yelledName()}!</h2>
<button onClick={() => nameStore1.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore1.reverseName()}>Reverse Name</button>
<h2>Hello {name2.yelledName()}!</h2>
<button onClick={() => nameStore2.set('Bilbo')}>Change Name</button>
<button onClick={() => nameStore2.reverseName()}>Reverse Name</button>
<button onClick={onResetBoth}>Reset Both</button>
{bothBobs() && <h2>Hello Bobs!</h2>}
</>
);
}