react-source-of-truth
As this library aims for maximum simplicity and small API there is no much more than reading the state and updating it. But there is one common pitfall you need to keep in mind
When you are updating the state you need to pass a selector function that points to the part of the state you want to
update. It is very similar to the selector function of useTruthSelector
but there is a difference - useTruthSelector
gets your plain state object untouched. update
method however reads the property you are reading as you go deeper and
deeper into your state.
With just an objects in your path this is straightforward:
interface State {
ui: {
modal: {
open: boolean
}
}
}
useTruthSelector(s => s.ui.modal.open); // read value
update(s => s.ui.modal.open, p => !p); // toggle value
Things get complicated when you meet an array or optional property on your way:
interface State {
articles: Array<{ title: string; author: string }>
}
This works:
useTruthSelector(s => s.articles[0].title); // gets first article title
update(s => s.articles[0].title, t => "News of the day: " + t); // prepends a text to the title
But this fails:
useTruthSelector(s => s.articles.find(a => a.author === "John").title); // gets title of first article by John - this is OK!
update(s => s.articles.find(a => a.author === "John").title, t => "News of the day: " + t); // you just messed up the state :(
Why? Because you are reading articles
and then find
property of the state. And this is the path you will be
updating. This is a trade-off for having such a simple way to update deep property of and object, keeping immutability
and full TypeScript support.
The fix:
const state = store.getState();
const indexOfArticle = state.articles.findIndex(a => a.author === "John");
update(s => s.articles[indexOfArticle].title, t => "News of the day: " + t); // All good now
Another example - optional property this time:
interface State {
temperature: {
kitchen?: {
lastRead: number;
value: number;
},
bedroom?: {
lastRead: number;
value: number;
},
}
}
This fails when current state is:
const currentState = {
temperature: {},
};
// ^ valid state, according to types
useTruthSelector(s => s.temperature.kitchen?.value); // gets kitchen temperature or undefined
update(s => s.temperature.kitchen?.value, () => 23); // state gets messed up again
Again, why? Because you are reading temperatures
and then kichen
property of the state. value
never gets read,
because kitchen
wasn't there yet. So you will be setting temperature.kitchen
path to 23
.
The fix:
const state = store.getState();
if (!state.temperature.kitchen) {
update(s => s.temperature.kitchen, () => ({})); // set it to empty object first
}
update(s => s.temperature.kitchen.value, () => 23); // all good now
update(s => s.temperature.kitchen.lastRead, () => Date.now()); // let's update something more
Don't worry about useless re-renders - if you are synchronously updating the state - it will result with just one state update for React.
These updates are a bit troublesome, but I have some ideas how to improve the API to take account for this.
Generated using TypeDoc