Deep down the rabbit hole of state management and server cache
Natalia Tepluhina
Principal Engineer
Core Team Member
Google Dev Expert
@N_Tepluhina
State management is complicated
Pinia
Vue
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {
return {
counter: 0,
}
},
})
export const useStore = defineStore('main', {
state: () => {
return {
counter: 0,
}
},
actions: {
increment() {
this.counter++
},
},
})
- synchronous
- does not create a lot of boilerplate
- has one source of changes
- non-persistent
"Curiouser and curiouser!"
Alice
Looks simple, right?
export const useStore = defineStore('characters', {
state: () => {
return {
characters: [],
loading: false,
error: null,
}
},
})
export const useStore = defineStore('characters', {
state: () => {
return {
characters: [],
loading: false,
error: null,
}
},
actions: {
async fetchCharacters() {
this.loading = true
try {
const response = await getCharacters()
this.characters = response.data
} catch (error) {
this.error = error
}
this.loading = false
},
},
})
Now let's add it to both components!
import { useStore } from '../store'
const store = useStore()
store.fetchCharacters()
Woops
Ok fine let's deduplicate
export const useStore = defineStore('characters', {
state: () => {
/* ... */
},
actions: {
/* ... */
},
getters: {
fetchStarted: (state) => state.characters.length > 0 || state.loading,
}
})
if (!store.fetchStarted) {
store.fetchCharacters()
}
[
{
id: "1",
name: "Alice",
image_url: "https://image1.png",
},
{
id: "2",
name: "The Cheshire Cat",
image_url: "https://image2.png",
},
]
[
{
id: "1",
name: "Alice",
image_url: "https://image1.png",
occupation: "White Pawn",
address: "London",
description: "Alice is a very lovely, pretty and beautiful young girl..."
},
{
id: "2",
name: "The Cheshire Cat",
image_url: "https://image2.png",
},
]
Sounds easy?
actions: {
async fetchCharacter(id) {
this.loading = true
try {
const response = await getCharacter(id)
const existingCharacter = this.characters.find((t) => t.id === id)
if (existingCharacter) {
const { description, address, occupation } = response.data
existingCharacter.description = description
existingCharacter.address = address
existingCharacter.occupation= occupation
} else {
this.characters.push(response.data)
}
} catch (error) {
this.error = error
}
this.loading = false
},
},
actions: {
async fetchCharacters() {
this.loading = true
try {
const response = await getCharacters()
this.characters = response.data
} catch (error) {
this.error = error
}
this.loading = false
},
},
actions: {
async fetchCharacters() {
this.loading = true
try {
const response = await getCharacters()
this.characters = response.data.reduce((acc, current) => {
const existingCharacter = acc.find((t) => t.id === current.id)
if (!existingCharacter) {
return [...acc, current]
}
return acc
}, this.characters)
} catch (error) {
this.error = error
}
this.loading = false
},
},
Now we're ok?
Haha no
getters: {
fetchStarted: (state) => state.characters.length > 0 || state.loading,
}
getters: {
fetchStarted: (state) => state.characters.length > 1 || state.loading,
}
"If you want to get somewhere else, you must run at least twice as fast as that!"
Red Queen
- how to fetch only when data is not fetched already?
- how to keep my data up-to-date?
- what if two components start fetching simultaneously?
Not all states are born equal!
Global state
Global state
Server cache
Local state
Apollo Client
"The best way to explain it
is to do it"
Dodo
Thank you!
NataliaTepluhina
@N_Tepluhina
Deep into server cache
By Natalia Tepluhina
Deep into server cache
- 325