To achieve derived local state when using ReactiveVar's you must define a field policy on the InMemoryCache.
Example
Let's create some local state
```TypeScript
import { makeVar } from "@apollo/client";
interface Item {
name: string;
price: number;
}
export const items = makeVar>([
{ name: "GameBoy Pocket", price: 50 },
{ name: "GameBoy Advance", price: 30 },
]);
```
I want a derived total that is only calculated when my item list changes, I need to do this where I define my cache using a pretty different API
```TypeScript
import { InMemoryCache } from "@apollo/client";
import { items } from "./state";
export const cache: InMemoryCache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
total: {
read() {
return items().reduce((acc, item) => acc + item.price, 0);
}
}
}
}
}
});
```
Then to use this I must write a GQL query
```tsx
const GET_TOTAL = gql`
query GetTotal {
total @client
}
`;
const ListSummary = () => {
const getTotalResult = useQuery(GET_TOTAL);
return (
Total: £{getTotalResult.data.total}
);
};
```
Why is this a problem?
I see some problems with this approach:
The FieldPolicy API feels very different from that of Reactive Variables - I can see that it makes sense when I want to export the value for use in a query, or attach the derived value to a type defined in a Schema, but sometimes (as in the above example) I really only want to use the derived value locally and associating it with the Cache feels unnecessary
Yes, you could easily use useMemo but if you then want to use the derived state in multiple places you'd still be deriving it multiple times. As soon as you try and avoid this I think you stray into developing a custom solution like the below proposed solution
I'm not sure if I'm missing something - please tell me if so - but it seems unless I'm associating the derived field with an existing Schema I need to just associate the field with the base Query. It would be nicer having a bit more control over the structure and organisation of local state
I could see a scenario where the same configured Apollo Client is shared amongst multiple Micro Front-Ends within an organisation, however you might want to also use AC for the local state management within each of these MFE's. In this scenario you might want derived local state that isn't coupled to the cache
I have to write a GQL query to get my derived state out of the cache
In the process I've lost type safety, I can pass a type into useQuery but if GQL and the cache were avoided all the types could be inferred
I have to dereference any returned value e.g. queryResult.data.myDerivedValue
useQuery makes it less explicit I'm depending on local state. It looks exactly the same as an actual query, except for the fact I'm not handling error and loading states
Most other local state management solutions solve this for you in a similar fashion to the one I'm proposing
Suggested Solution
An API for creating a DerivedReactiveVar. Something akin to reselect.
interface DerivedReactiveVar<T> {
(): T;
}
// allow DerivedReactiveVar and ReactiveVar to be used interchangeably
type AllReactiveVars<T> = ReactiveVar<T> | DerivedReactiveVar<T>;
// [AllReactiveVars<number>, AllReactiveVars<string>] => [number, string]
type ExtractReactiveVarTupleTypes<
T extends ReadonlyArray<AllReactiveVars<any>>
> = { [K in keyof T]: T[K] extends AllReactiveVars<infer V> ? V : never };
// Almost exactly the same as reselect, used an array to avoid having to use type overloading
function makeDerivedVar<V extends AllReactiveVars<any>[], T extends [...V], O>(
reactiveVars: T,
combiner: (...args: ExtractReactiveVarTupleTypes<T>) => O
): DerivedReactiveVar<O> {
// ... magic implementation
return (() => {}) as DerivedReactiveVar<O>;
}
From what I can understand this is pretty much a computed state. If this is the case, can we name this computedVar or something so that it's similar to the name used by other state managers?
Problem
To achieve derived local state when using
ReactiveVar
's you must define a field policy on theInMemoryCache
.Example
Let's create some local state ```TypeScript import { makeVar } from "@apollo/client"; interface Item { name: string; price: number; } export const items = makeVarWhy is this a problem?
I see some problems with this approach:
FieldPolicy
API feels very different from that of Reactive Variables - I can see that it makes sense when I want to export the value for use in a query, or attach the derived value to a type defined in a Schema, but sometimes (as in the above example) I really only want to use the derived value locally and associating it with the Cache feels unnecessaryuseMemo
but if you then want to use the derived state in multiple places you'd still be deriving it multiple times. As soon as you try and avoid this I think you stray into developing a custom solution like the below proposed solutionQuery
. It would be nicer having a bit more control over the structure and organisation of local statequeryResult.data.myDerivedValue
useQuery
makes it less explicit I'm depending on local state. It looks exactly the same as an actual query, except for the fact I'm not handling error and loading statesSuggested Solution
An API for creating a
DerivedReactiveVar
. Something akin toreselect
.Usage
Some Typing