-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement reactive variables for tracking local state. #5799
Conversation
Local variables are functions allow you to store local state in a well-known, private location, an update it whenever you like: const lv = cache.makeLocalVar(123) console.log(lv()) // 123 console.log(lv(lv() + 1)) // 124 console.log(lv()) // 124 lv("asdf") // type error Any parts of any queries that relied upon the value of the variable will be invalidated when/if the variable's value is updated.
public makeLocalVar<T>(value: T): LocalVar<T> { | ||
return function LocalVar(newValue) { | ||
if (arguments.length > 0) { | ||
if (value !== newValue) { | ||
value = newValue; | ||
localVarDep.dirty(LocalVar); | ||
} | ||
} else { | ||
localVarDep(LocalVar); | ||
} | ||
return value; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty proud of how short this implementation can be, thanks to our existing dependency tracking machinery.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay optimism
!! Thanks for working on this @benjamn - looks great!
Thanks @benjamn! this really makes the difference! I made a simple choice list example in a code sandbox to test that feature and it looks amazing: Before reactive variables (using two different approaches to achieve the desired behavior): After reactive variables: I will seriously considerate to switch from redux to apollo ;) |
Shorter, and not as suggestive of Apollo Client 2.x local state. Returns a function of type ReactiveVar<T> (f.k.a. LocalVar<T>). Follow-up to #5799.
As an old-hand users & fan of the Flyd "minimalistic but powerful, modular, functional reactive programming library", I feel very at home & used to this! Flyd has a similar api, where |
The makeVar method was originally attached to InMemoryCache so that we could call cache.broadcastWatches() whenever the variable was updated. See #5799 and #5976 for background. However, as a number of developers have reported, requiring access to an InMemoryCache to create a ReactiveVar can be awkward, since the code that calls makeVar may not be colocated with the code that creates the cache, and it is often desirable to create and initialize reactive variables before the cache has been created. As this commit shows, the ReactiveVar function can infer the current InMemoryCache from a contextual Slot, when called without arguments (that is, when reading the variable). When the variable is updated (by passing a new value to the ReactiveVar function), any caches that previously read the variable will be notified of the update. Since this logic happens at variable access time rather than variable creation time, makeVar can be a free-floating global function, importable directly from @apollo/client. This new system allows the variable to become associated with any number of InMemoryCache instances, whereas previously a given variable was only ever associated with one InMemoryCache. Note: when I say "any number" I very much mean to include zero, since a ReactiveVar that has not been associated with any caches yet can still be used as a container, and will not trigger any broadcasts when updated. The Slot class that makes this all work may seem like magic, but we have been using it ever since Apollo Client 2.5 (#3394, via the optimism library), so it has been amply battle-tested. This magic works.
Just a quick note that we've made this API even easier to use ( |
…6512) The makeVar method was originally attached to InMemoryCache so that we could call cache.broadcastWatches() whenever the variable was updated. See #5799 and #5976 for background. However, as a number of developers have reported, requiring access to an InMemoryCache to create a ReactiveVar can be awkward, since the code that calls makeVar may not be colocated with the code that creates the cache, and it is often desirable to create and initialize reactive variables before the cache has been created. As this commit shows, the ReactiveVar function can infer the current InMemoryCache from a contextual Slot, when called without arguments (that is, when reading the variable). When the variable is updated (by passing a new value to the ReactiveVar function), any caches that previously read the variable will be notified of the update. Since this logic happens at variable access time rather than variable creation time, makeVar can be a free-floating global function, importable directly from @apollo/client. This new system allows the variable to become associated with any number of InMemoryCache instances, whereas previously a given variable was only ever associated with one InMemoryCache. Note: when I say "any number" I very much mean to include zero, since a ReactiveVar that has not been associated with any caches yet can still be used as a container, and will not trigger any broadcasts when updated. The Slot class that makes this all work may seem like magic, but we have been using it ever since Apollo Client 2.5 (#3394, via the optimism library), so it has been amply battle-tested. This magic works.
Reactive local variables are functions that allow you to store local state in a well-known, private location, outside of the cache, consume that state while reading queries from the cache, and update the state whenever you like.
Basic usage:
TypeScript infers the value type of the variable based on the initial value, which is why switching from a number to a string is a type error here.
Any parts of any queries that relied upon the value of the variable will be invalidated when/if the variable's value is updated:
With this new primitive available, it should not be necessary to construct awkward queries just to read and write local state, since you can read the variable directly with
nameVar()
and update its value by callingnameVar(newValue)
.