Skip to content
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

Improve documentation for useSyncExternalStore #6530

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 32 additions & 5 deletions src/content/reference/react/useSyncExternalStore.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,18 +377,19 @@ Make sure that `getServerSnapshot` returns the same exact data on the initial cl

This error means your `getSnapshot` function returns a new object every time it's called, for example:

```js {2-5}
```js {3-5}
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
// 🔴 This creates a new object every time getSnapshot is
// called, even when myStore.todos has not changed
return {
todos: myStore.todos
};
}
```

React will re-render the component if `getSnapshot` return value is different from the last time. This is why, if you always return a different value, you will enter an infinite loop and get this error.
React will re-render the component if `getSnapshot` returns a different object than the last time it was called. Therefore, if you return a new object on every call, React will enter an infinite loop and raise this error.

Your `getSnapshot` object should only return a different object if something has actually changed. If your store contains immutable data, you can return that data directly:
Your `getSnapshot` object should only return a different object if the data in the external store has actually changed. If your store contains immutable data, you can return that data directly:

```js {2-3}
function getSnapshot() {
Expand All @@ -397,7 +398,33 @@ function getSnapshot() {
}
```

If your store data is mutable, your `getSnapshot` function should return an immutable snapshot of it. This means it *does* need to create new objects, but it shouldn't do this for every single call. Instead, it should store the last calculated snapshot, and return the same snapshot as the last time if the data in the store has not changed. How you determine whether mutable data has changed depends on your mutable store.
If the data in your store is mutable, your `getSnapshot` function should return an immutable snapshot of it. This means it *does* need to create new objects, but not for every single call. Instead, it should cache the last calculated snapshot, and return a new object only if the data in the store has changed. How you determine whether the data in your store has changed is specific to the store you're using.

<DeepDive>

#### Why does React re-render even if the values of your object have not changed? {/*why-does-react-re-render-even-if-the-values-of-your-object-have-not-changed*/}

JavaScript has a syntax called [object literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer), which are key-value pairs enclosed in curly braces (`{}`). This syntax creates a new object.

When comparing objects, JavaScript only checks if the two objects are the same object, not if their *contents* are equal. Since the object literal syntax creates a new object, comparing two objects created with the object literal syntax always gives `false`, even when the values are the same. For example:

```js
object1 = {
name: "John",
favoriteNumber: 42
};

object2 = {
name: "John",
favoriteNumber: 42
};

console.log(object1 == object2); // false
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is missing here that connects it back to why return {} is a new object every time, something like this:


This also applies to the return values of functions:

function getUser() {
  return {
    name: "John",
    favoriteNumber: 42
  };
}

const object1 = getUser();
const object2 = getUser();

console.log(object1 == object2); // false

And maybe add an example of hosting the object out to show how it doesn't change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples would be better as chat settings like in the explanations above rather than users.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rickhanlonii I'm not sure how I should change the code example. I want it to be clear to the reader that this isn't specific to getSnapshot handlers (we include examples of that above), but it needs to include returning an object from a function (like you said).

We also want to make it clear that the values of the retuned object are the same, but then it doesn't make sense why you would be comparing the returned values.

The best I can think of is something like this:

function getMathConstants() {
  return {
    pi: 3.14159,
    e: 2.71828
  }
}

let mathConstants1 = getMathConstants();
let mathConstants2 = getMathConstants();

console.log(mathConstants1 == mathConstants2);
// false

But that makes very little sense. And, like you said, it should probably be related to the example chat app.

Any suggestions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


This means that if your `getSnapshot` function returns a new object (like the first example above did), React will always think the object has changed, and will re-render the component.

</DeepDive>

---

Expand Down