Skip to main content

Caching Queries

BobaBoard's frontend uses React Query as its query caching mechanism. See our knowledge base for basic information on caches.

Manual Cache Operations

React Query automatically manages the caching and refetching of queries. That said, manual operations are required on occasion:

  1. Optimistic Updates: we manually write the updated values in the existing cache after receiving new data from the user. This gives the illusion of an immediate update while we wait for server confirmation.
    • Example: showing the updated board description in the sidebar as soon as the "save" button is clicked.
  2. Data Preloading: to preload the (potentially partial) result of a query when partial data exists in already-loaded ones.
    • Example: showing the first post in a thread (loaded from the board feed data) while waiting for the full thread to load.

Where to Define Manual Cache Operations

All caches operation are definined in the /cache directory. Each entity (e.g. thread, board) should have its own file named as the entity (singular).

Getter and Setters

The cache methods should use the following patterns:

  • get[entity]InCache: to retrieve entity data.
  • set[entity]InCache: to update entity data.
  • add[entity]InCache: to add a new entity data.

The Transformer Pattern

Most often update operations have to be repeated by multiple functions across multiple caches. To ensure all functions consistently update an entity across all caches it appears in, we use a transformer pattern:

const set[entity]InCache = ({
queryClient: QueryClient,
{ entityId }: { entityId: string },
transform: (oldEntity: EntityType) => EntityType
}) => {
// Find all instance of "entity" in all caches by using the given id.
[...]

// Get the updated value of the old entity
const newEntity = transform(oldEntity);
if (newEntity !== oldEntity) {
// If transformer returns an updated entity, update the caches with the new values
}
}
important

The transform method must return:

  • The same instance of the entity if no update was done.
  • A new instance of the entity if an update was done.

This ensures React's re-rendering logic correctly picks up (or ignores) the change.

Our update methods can then use different transformers to update the data:

const setBoardMutedInCache = ({
queryClient: QueryClient,
{ boardId }: { boardId: string }) => {
setBoardInCache({
queryClient,
{boardId},
// The transformer
(board: Board) => {
if (board.muted) {
// The board is already muted, so we return the same object.
return board;
}
// We return a *new* object with the same property as board, but
// the "muted" property set to true.
return {
...board,
muted: true;
}
}
})
}
const setBoardVisibleInCache = ({
queryClient: QueryClient,
{ boardId }: { boardId: string }) => {
setBoardInCache({
queryClient,
{boardId},
// The transformer
(board: Board) => {
if (!board.hidden) {
// The board is already visible, so we return the same object.
return board;
}
// We return a *new* object with the same property as board, but
// the "hidden" property set to false.
return {
...board,
hidden: false;
}
}
})
}

Useful React Query Methods

If you're working with the cache, it might be useful to familiarize yourself with the following methods:

The best way to understand how to use them is to look at the existing implementations of set[Entity]InCachecache methods.