State: Single Source of Truth
2 mins |
September 17, 2021It is always advised to have a single source of truth for your application. Before we start, let’s see what exactly the state of the application is.
State
is part of the application that holds a source of data. It could be your
database like Table
, Document
or React states like useState
or
useReducer
.
Single Source of Truth
Any time you query for data, it should be fulfilled by a unique set of State
.
For eg. If you ask for List of all users who have commented on a post, you
should get a list of users from set tables such that this information is not
replicated in any other data.
1select *2from users u3inner join post_comments pc on pc.user_id = u.user_id4where pc.post_id = 1
Here, we are querying from two tables, users
and post_comments
. This is
fine, as none of the information is repeated;
Examples where you might not have a single source of truth
There might be a lot of cases where we might not be having a single source of truth. It could be an aggregate table, a cache, a data copied from source props, or requesting data from an AJAX request. In all of these instances, we are copying data from one source to another. There are many other instances where you might be doing the same.
But if you had created an aggregate table, users_comments
, which has all the
comments of a given user, then we will have two sources of data for the same
query.
1/* Query 1 */2select *3from users u4inner join post_comments pc on pc.user_id = u.user_id5where pc.post_id = 167/* Query 2 */8select *9from users_comments10where post_id = 1
Essentially both Query 1
and Query 2
are returning the same data.
Another example
1function NumberList() {2 const [numbers, setNumbers] = useState()3 return <Child state={state} />4}56function EvenNumbers({ numbers }) {7 const [evenNumbers, setEvenNumbers] = useState()8 useEffect(() => {9 setEvenNumbers(numbers.filter(filterEvenNumbers))10 }, [numbers])11}
Let’s say we have a list of numbers, and we want to filter out the numbers that
are even in the EvenNumbers
component.
Here, evenNumbers
copied from numbers
, But there is a possibility that these
sources can diverge in the future. Maybe a bug in code, you come infrastructure
outage caused write in one of the tables failed.
There is nothing that ensures numbers
and evenNumbers
hold the same source
of truth. You might be thinking useEffect
ensures that. But it is not. It is
creating a copy of data, there is no check to make sure that useEffect
is not
removed in the future.
For eg.
When numbers
was null
our code broke, so add a check to make sure that if it
is null then copy doesn’t happen.
1function NumberList() {2 const [numbers, setNumbers] = useState()3 return <Child state={state} />4}56function EvenNumbers({ numbers }) {7 const [evenNumbers, setEvenNumbers] = useState()8 useEffect(() => {9+ if(!state) return10 setEvenNumbers(numbers.filter(filterEvenNumbers))11 }, [numbers])12}
But this introduces a new problem. In this example, lets say numbers
is
[2]
and we sync it to evenNumbers
so it will be [2]
. Now, let us update
the numbers
to null
. Now because of the check we introduced in useEffect
,
copyState
will be [2]
and state will be null
. Our state is not in
sync.
Syncing state
Whenever you have an observer listening to data and it updating another source
of data. Then it is called syncing data. For eg. in our above react example,
we are syncing using useEffect
. In caching it could be our cache
invalidation logic, in the case of aggregate table, it could the triggers.
By definition, we have a separate function to sync data. This implies that there is a possibility that our sync logic might not run. It could be because some developers decided to add some logic to sync data.
If possible we should be deriving data from the source instead of copying data. For eg.
1function NumberList() {2 const [numbers, setNumbers] = useState()3 return <Child state={state} />4}56function EvenNumbers({ numbers }) {7- const [evenNumbers, setEvenNumbers] = useState()8+ const evenNumbers = numbers.filter(filterEvenNumbers);9- useEffect(() => {10- if(!state) return11- setEvenNumbers(numbers.filter(filterEvenNumbers))12- }, [numbers])13}
Avoid copying state
- Avoid copying state, instead derive state from the source. Copying introduce a latent bug.
- If you have to copy state, make sure that you have additional checks to make sure sync is happening.
Got a Question? Bala might have the answer. Get them answered on #AskBala
Subscribe Now! Letters from Bala
Subscribe to my newsletter to receive letters about some interesting patterns and views in programming, frontend, Javascript, React, testing and many more. Be the first one to know when I publish a blog.
No spam, just some good stuff! Unsubscribe at any time