Comments (6)
Fwiw/afaiu this also easily/regularly happens with any library that is trying to bring observables/signals to React, i.e. Mobx or Legend State, and wants to cross the "React props <=> observables/signal world" divide.
For example, if I have two components:
- Parent component
<Author name=bob />
and - Child component
<AuthorTitle author=authorSignal />
Where the Author
component internally initializes an observable/signal, and then passes the authorSignal
around to other components.
If the Author
is passed a new name prop, and naively does:
function Author({ name }) {
// create a stable observable on 1st render
const author = useMemo(() => observable({ name });
// Keep the observable updated on Nth render
author.name = name;
// render name + any components using the new name
return <div>{name} <AuthorTitle author={author} /></div>
});
Then the author.name = name
line will cause this warning that the AuthorTitle
reactive infra (i.e. a useComputed
or observer
HOC, whether either useState
/tick-based or useSES
-based) registered a change for AuthorTitle
to re-render.
Granted, the correct behavior is to shunt all "maybe updating an observable" operations through useEffect
s:
function Author({ name }) {
// create a stable observable
const author = useMemo(() => observable({ name });
// Keep the observable updated
useEffect(() => author.name = name, [name]);
// render the potentially-updated name
return <div>{name} <AuthorTitle author={author} /></div>
});
But that is tedious, boilerplatey, and can also cause double renders if Author
is itself using a calculated value from the observable:
function Author({ name }) {
// create a stable observable
const author = useMemo(() => observable({
name,
// use some reactive/computed business logic
initials: () => getInitials(this.name)
});
// Keep the observable updated
useEffect(() => author.name = name, [name]);
// When props.name changes, we'll do one render with new name +
// old initials (not updated yet) and then after useEffect has
// updated the observable, a 2nd render that has the non-torn
// new name/new initials
return <div>{name} {author.initials} <AuthorTitle author={author} /></div>
});
This update-observables-via-useEffect
pales particularly to the props-based way of telling AuthorTitle
to re-render, which is just "pass the prop via JSX":
return <div>{name} <AuthorTitle authorName={name} /></div>
So, using props/JSX to tell AuthorTitle
to render during my render is fine, but I'm unable to tell AuthorTitle
to render during my render if I'm using an observable.
Granted, I get it, in React props are first-class and observables are not, so I shouldn't be surprised that observables have worse ergonomics.
But imo this dichotomy is really what I expected useSES
to fix, b/c fundamentally it focus on components needing to "reactively" re-render based on "other things happening", i.e. external syncs, typically AJAX updates, 3rd-party-stores whatever.
Because, for those of us pretending to write Solid in React :-D, the props of components like Author
(that cross the props/observable divide), if you squint, are essentially their own "external source of change" for components that are consuming the observable.
Hence, I think this warning should be removed for useSES
.
from react.
@Nodge : are you manually calling useSyncExternalStore
?
Is there a reason you're not using React-Redux for that? (It already relies on uSES
internally)
Also, in general it looks like the pattern you're using definitely breaks the rules of React rendering.
You can call setState()
while rendering, in very specific cases, to force an immediate re-render:
But you can't trigger renders for other components while rendering, and you shouldn't be dispatching global actions while rendering either.
from react.
I simplified the example code to only include the necessary parts that reproduce the problem. But in my actual project, Iām using slightly modified versions of React-Redux and RTKQ, to integrate it with Suspense for data fetching. Basically, during the render phase, the only action I dispatch is the RTKQ endpoint initiate which gives me a promise for Suspense integration. I understand that dispatching global actions on the render phase is an anti-pattern, and I should find a way to rework it. I agree that the warning is not a bug in React.
The only thing left to address in this issue is the warning logging behavior. Should React log the warning more consistently? (see "strange behavior 1,2" in the description)
from react.
@Nodge huh. Getting off-topic, but: "modified versions of React-Redux / RTKQ for Suspense"?
Could you file a discussion thread over in the Redux Toolkit repo to discuss what you're trying to do? We don't have an official Suspense solution yet, largely because too many things with React have been changing and it's unclear how we're supposed to support things. But, it would be nice to see what kinds of use cases people have and what you're trying to do to play with those.
(Not sure on what's up with the warning behavior, tbh.)
from react.
I seem to have the same problem. Everything renders fine, but the warning frightens me a bit. Here's a simplified playground:
The store looks like this:
/*
Here we store <select> options in the form of:
{
[name]: {
loading: bool // if the options are still loading from some backend
options: [...] // list of string values
}
}
*/
let snapshot = {};
// Array of listeners of the store
let listeners = [];
const Store = {
setValuesLoading(name, silent = false) {
snapshot = { ...snapshot, [name]: { loading: true, options: [] } };
if (!silent) {
notify();
}
},
setOptions(name, options) {
snapshot = { ...snapshot, [name]: { loading: false, options } };
notify();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter(l => !Object.is(l, listener));
};
},
getSnapshot() {
return snapshot;
}
};
// Trivial listener notifier.
const notify = () => {
for (let listener of listeners) {
listener();
}
};
To use it, I've written a custom hook, that triggers an "API" to load data if necessary:
export function useOptions(name) {
let store = useSyncExternalStore(Store.subscribe, Store.getSnapshot);
// If data is in cache ā return it
if (store[name]) {
return store[name];
}
// Updating the store, do not notify anyone (we are within render currently)
Store.setValuesLoading(name, false);
// loading data
loadOptions(name).then(options => Store.setOptions(name, options));
return { loading: true, options: [] };
}
// Imitate API fetch
function loadOptions(name) {
return new Promise(resolve => {
setTimeout(() => resolve(["one", "two", "three"]), 1000);
});
}
Usage is trivial:
import { useOptions } from './store.jsx';
export function Select({ name }) {
let { options } = useOptions(name);
return (
<select>
{options.map(opt => (
<option value={opt} key={opt}>
{opt}
</option>
))}
</select>
);
}
The problem somehow happens ONLY if I dynamically add a Select
. Multiple Select
's, rendered statically, don't cause any warnings.
import React, { useState } from 'react';
import { Select } from './Select'
export function App(props) {
let [show, setShow] = useState(false)
return (
<div className='App'>
<Select name="one" />
<Select name="two" />
<button onClick={() => setShow(true)}>CLICK ME TO TRIGGER A WARNING IN CONSOLE</button>
{show ? <Select name="three" /> : null}
</div>
);
}
React 18.2.0
from react.
I seem to have the same problem.
from react.
Related Issues (20)
- Bug: Class component with `defaultProps` not merging props correctly in `componentDidMount` HOT 2
- Bug: React-ts can't support sass, which makes people suffer from obsessive-compulsive disorder! HOT 1
- Bug: promises passed to server actions block on promise parameters
- Bug: server actions cannot be cancelled or given abort signals HOT 2
- Bug: Property 'inputType' does not exist on type 'Event' HOT 5
- Bug: error boundary does not catch exceptions from 'true' branch of an if-statement
- Bug:
- Bug: ESLint rule doesn't catch mistake with non-exhaustive dependencies
- Fix gap between footer logo and links HOT 16
- Source is not showing HOT 7
- Bug: Hydration error due to <button> HOT 6
- Bug: Search functionality not working correctly on react.dev in Safari on iOS real device (iPhone 12 Pro) HOT 2
- Bug:
- Google Translate causing "Text content does not match server-rendered HTML" HOT 1
- Bug: useFormState formAction becomes null in strict mode HOT 1
- Bug: Source is not showing in 5.0.2 HOT 9
- Question and suggest: The official identifier of Server Compnoent and Client Component HOT 3
- Bug: when installing the application, the old version of "react-scripts" is installed HOT 1
- Bug: Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string. HOT 4
- Bug: optimistic state (useOptimistic) shows both optimistic and returned from server data when running several async actions HOT 6
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
š Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ššš
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ā¤ļø Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react.