From c56e3524c3a51bb83571d0d737a57f3b16e53839 Mon Sep 17 00:00:00 2001 From: Larry Myers Date: Wed, 26 Oct 2022 21:28:50 -0500 Subject: [PATCH 1/3] Add troubleshooting tips to the reactive var documentation. This PR adds examples on how to avoid some common pitfalls when using reactive vars. It should hopefully help people get some time back when trying to triage issues with reactive vars mutations not triggering component updates in React applications. --- .../source/local-state/reactive-variables.mdx | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/source/local-state/reactive-variables.mdx b/docs/source/local-state/reactive-variables.mdx index 596df3eefc6..e4afa1b75dc 100644 --- a/docs/source/local-state/reactive-variables.mdx +++ b/docs/source/local-state/reactive-variables.mdx @@ -75,6 +75,60 @@ export function Cart() { // ... ``` +## Troubleshooting + +### Updating Arrays + +Currently `useReactiveVar` only triggers component updates when new values are passed to the reactive var, since it uses strict equality checks (the `===` operator). Make sure you are alway passing cloned values when performing an update. + +Will __not__ trigger an update: + +```ts +const listVar = makeVar([1, 2]); +const list = listVar(); + +list.push(3); +listVar(list); +``` + +Will trigger an update: + +```ts +const listVar = makeVar([1, 2]); +const list = listVar(); + +const nextList = [...list, 3]; +listVar(nextList); +``` + +### Updates Outside of Components + +If you are making updates to a reactive variable outside of a React component and your components that use `useReactiveVar` are not updating, try deferring the update to the next tick of the event loop. + +Here's an example that tracks GraphQL errors using the [onError Link](../api/link/apollo-link-error.md). + +```ts +const errorsVar = makeVar([]); + +const errorLink = onError((error: ErrorResponse) => { + const errors = errorsVar(); + + // making a copy of the current array value will trigger an update + const nextErrors = [...errors]; + + if (error.graphQLErrors) { + error.graphQLErrors.forEach((e) => nextErrors.push(e)); + } + + if (error.networkError) { + nextErrors.push(error.networkError); + } + + // deferring the update to next tick will allow the update to reliably trigger a new component render + setTimeout(() => errorsVar(nextErrors)); +}); +``` + ## Example application -[This example to-do list application](https://github.com/apollographql/ac3-state-management-examples/tree/master/apollo-local-state) uses reactive variables to track both the current list of to-do items and the filter that determines which items to display. See [`cache.tsx`](https://github.com/apollographql/ac3-state-management-examples/blob/master/apollo-local-state/src/cache.tsx) in particular. \ No newline at end of file +[This example to-do list application](https://github.com/apollographql/ac3-state-management-examples/tree/master/apollo-local-state) uses reactive variables to track both the current list of to-do items and the filter that determines which items to display. See [`cache.tsx`](https://github.com/apollographql/ac3-state-management-examples/blob/master/apollo-local-state/src/cache.tsx) in particular. From b888e6668e07e151741d37d68e7fbf63b740e70a Mon Sep 17 00:00:00 2001 From: Larry Myers Date: Tue, 15 Nov 2022 08:18:09 -0600 Subject: [PATCH 2/3] Updates from PR review: * Spelling corrections * Move array example up to Modifying section. --- .../source/local-state/reactive-variables.mdx | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/docs/source/local-state/reactive-variables.mdx b/docs/source/local-state/reactive-variables.mdx index e4afa1b75dc..9f9085de7a9 100644 --- a/docs/source/local-state/reactive-variables.mdx +++ b/docs/source/local-state/reactive-variables.mdx @@ -52,6 +52,28 @@ cartItemsVar([456]); console.log(cartItemsVar()); ``` +In the above example note that a new array is passed in every time to the reactive variable. Currently `useReactiveVar` only triggers component updates when new values are passed to the reactive variable. It uses strict equality checks (the `===` operator) to determine if the value has changed. Make sure you are always passing new values when performing an update. + +This will *not* trigger an update: + +```ts +const cartItemsVar = makeVar([1, 2]); +const cartItems = cartItemsVar(); + +cartItems.push(3); +cartItemsVarVar(cartItems); +``` + +This will trigger an update: + +```ts +const cartItemsVar = makeVar([1, 2]); +const cartItems = cartItemsVar(); + +cartItemsVar([...cartItems, 3]); // spread operator to use existing values +cartItemsVar(cartItems.concat(4)); // Array.concat creates a new array +``` + ## Reacting As their name suggests, reactive variables can trigger reactive changes in your application. Whenever you modify the value of a reactive variable, queries that depend on that variable refresh, and your application's UI updates accordingly. @@ -77,30 +99,6 @@ export function Cart() { ## Troubleshooting -### Updating Arrays - -Currently `useReactiveVar` only triggers component updates when new values are passed to the reactive var, since it uses strict equality checks (the `===` operator). Make sure you are alway passing cloned values when performing an update. - -Will __not__ trigger an update: - -```ts -const listVar = makeVar([1, 2]); -const list = listVar(); - -list.push(3); -listVar(list); -``` - -Will trigger an update: - -```ts -const listVar = makeVar([1, 2]); -const list = listVar(); - -const nextList = [...list, 3]; -listVar(nextList); -``` - ### Updates Outside of Components If you are making updates to a reactive variable outside of a React component and your components that use `useReactiveVar` are not updating, try deferring the update to the next tick of the event loop. @@ -112,9 +110,9 @@ const errorsVar = makeVar([]); const errorLink = onError((error: ErrorResponse) => { const errors = errorsVar(); - + // making a copy of the current array value will trigger an update - const nextErrors = [...errors]; + const nextErrors = [...errors]; if (error.graphQLErrors) { error.graphQLErrors.forEach((e) => nextErrors.push(e)); @@ -125,7 +123,7 @@ const errorLink = onError((error: ErrorResponse) => { } // deferring the update to next tick will allow the update to reliably trigger a new component render - setTimeout(() => errorsVar(nextErrors)); + setTimeout(() => errorsVar(nextErrors)); }); ``` From a28a84d4d7c6ecf93d839fc718d666621708fd9c Mon Sep 17 00:00:00 2001 From: Larry Myers Date: Fri, 18 Nov 2022 06:14:12 -0600 Subject: [PATCH 3/3] Remove troubleshooting section, update Modifying section with more concise example. --- .../source/local-state/reactive-variables.mdx | 62 +++---------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/docs/source/local-state/reactive-variables.mdx b/docs/source/local-state/reactive-variables.mdx index 9f9085de7a9..3fdbbfa53d7 100644 --- a/docs/source/local-state/reactive-variables.mdx +++ b/docs/source/local-state/reactive-variables.mdx @@ -40,40 +40,22 @@ To modify the value of your reactive variable, call the function returned by `ma ```js const cartItemsVar = makeVar([]); +const cartItemIds = [100, 101, 102]; -cartItemsVar([100, 101, 102]); +cartItemsVar(cartItemIds); // Output: [100, 101, 102] console.log(cartItemsVar()); -cartItemsVar([456]); +// Don't mutate an existing object or array. +// cartItemIds.push(456) will not trigger an update. +// Instead, pass a new copy: +cartItemsVar([...cartItemIds, 456]); -// Output: [456] +// Output: [100, 101, 102, 456] console.log(cartItemsVar()); ``` -In the above example note that a new array is passed in every time to the reactive variable. Currently `useReactiveVar` only triggers component updates when new values are passed to the reactive variable. It uses strict equality checks (the `===` operator) to determine if the value has changed. Make sure you are always passing new values when performing an update. - -This will *not* trigger an update: - -```ts -const cartItemsVar = makeVar([1, 2]); -const cartItems = cartItemsVar(); - -cartItems.push(3); -cartItemsVarVar(cartItems); -``` - -This will trigger an update: - -```ts -const cartItemsVar = makeVar([1, 2]); -const cartItems = cartItemsVar(); - -cartItemsVar([...cartItems, 3]); // spread operator to use existing values -cartItemsVar(cartItems.concat(4)); // Array.concat creates a new array -``` - ## Reacting As their name suggests, reactive variables can trigger reactive changes in your application. Whenever you modify the value of a reactive variable, queries that depend on that variable refresh, and your application's UI updates accordingly. @@ -97,36 +79,6 @@ export function Cart() { // ... ``` -## Troubleshooting - -### Updates Outside of Components - -If you are making updates to a reactive variable outside of a React component and your components that use `useReactiveVar` are not updating, try deferring the update to the next tick of the event loop. - -Here's an example that tracks GraphQL errors using the [onError Link](../api/link/apollo-link-error.md). - -```ts -const errorsVar = makeVar([]); - -const errorLink = onError((error: ErrorResponse) => { - const errors = errorsVar(); - - // making a copy of the current array value will trigger an update - const nextErrors = [...errors]; - - if (error.graphQLErrors) { - error.graphQLErrors.forEach((e) => nextErrors.push(e)); - } - - if (error.networkError) { - nextErrors.push(error.networkError); - } - - // deferring the update to next tick will allow the update to reliably trigger a new component render - setTimeout(() => errorsVar(nextErrors)); -}); -``` - ## Example application [This example to-do list application](https://github.com/apollographql/ac3-state-management-examples/tree/master/apollo-local-state) uses reactive variables to track both the current list of to-do items and the filter that determines which items to display. See [`cache.tsx`](https://github.com/apollographql/ac3-state-management-examples/blob/master/apollo-local-state/src/cache.tsx) in particular.