-
Notifications
You must be signed in to change notification settings - Fork 91
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
React errors in DEV mode #237
Comments
I agree with this. We should be calling I don't think you'll run into an issue with using export const createNotifier =
(
onResize: Props['onResize'],
size: ReactResizeDetectorDimensions,
setSize: React.Dispatch<React.SetStateAction<ReactResizeDetectorDimensions>>,
handleWidth: boolean,
handleHeight: boolean
) =>
({ width, height }: ReactResizeDetectorDimensions): void => {
if (size.width === width && size.height === height) {
// skip if dimensions haven't changed
return;
}
if ((size.width === width && !handleHeight) || (size.height === height && !handleWidth)) {
// process `handleHeight/handleWidth` props
return;
}
onResize?.(width, height);
setSize({ width, height });
}; Then in the const notifyResize = createNotifier(onResize, size, setSize, handleWidth, handleHeight); |
…ater function in \`createNotifier\`. The updater function must be pure.
@kostia1st I created a PR for this issue 😁 #239. Hopefully we can get it merged |
@kostia1st Thank you for such a detailed issue As @kostia1st suggested I simply moved "onResize" to "useEffect" 8730195#diff-045dde5368069c113c4ac044407e3cba3f7cc3db1c6f0e166514b1cc9f5cf28fR72-R74 This should do the trick. |
@maslianok Thanks so much! My only concern with that approach is that the *I've not done any performance tests, but since the ResizeObserver can trigger so frequently, I can imagine a noticeable impact to performance. But the nature of performance is not to bother optimizing too much unless you are noticing a problem or have numbers to back it up, so that may be a moot point. |
…ater function in \`createNotifier\`. The updater function must be pure.
@tylerprice1 Good point. Actually I think your approach is better 👍 I resolved conflicts in the PR and left one comment. |
Issue #237 - Do not call onResize during setSize updater function in createNotifier
@tylerprice1 Unfortunately, your approach has some pitfalls. Imagine we have the next use case: component subscribes to onResize events to change some state. const [count, setCount] = useState(0);
const { width, height, ref } = useResizeDetector({
onResize: () => {
console.log(count);
setCount(count + 1);
}
}); What actually happens (step-by-step):
|
Let me add, that the previous version had the same problems. But the current one, based on a separate |
You're right. We can do a similar thing to what I did in my PR with
Then we can just pass the ref around so that the effect always has the updated value without having to recreate the ResizeObserver. Although your current advice to wrap the |
Oh, that's interesting! Do you see any downsides of this approach? Do you think it's better to call onResizeRef.current?.(width, height);
setSize(newSize);
// or
setSize(newSize);
onResizeRef.current?.(width, height); Won't we have problems with the following code snippet? const { width,height } = useResizeDetector({ onResize });
const onResize = () => {
setSomeParentState(width,height); // <---- old width & height
} |
DownsidesTo me, the only real downside to using refs like this is it may be confusing if you don't know what's going on, but I don't think that'll be an issue here, and I tried to document
*They mention using Refs for this in their legacy documentation (I can't find it in their new documentation) https://legacy.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often Something like this in a utils file: function useRefToReactiveValue<T>(value: T): MutableRefObject<T> {
const ref = useRef<T>(value);
ref.current = value;
return ref;
} Then in const sizeRef = useRef(size);
sizeRef.current = size; with const sizeRef = useRefToReactiveValue(size); And we can add: const onResizeRef = useRefToReactiveValue(onResize); OrderingI don't think the ordering would matter at all, but I'd probably set my own state before notifying the consumer like setSize(newSize);
onResizeRef.current?.(width, height);
|
Another thought I'm having that I wanted to put in a separate comment I think the "React" way to do this would be to lift the state up. What this would mean here is either:
The downside of either of these approaches is that it would be a breaking API change An example of how using 🚫 Bad code function Parent() {
const [size, setSize] = useState({ width: 0, height: 0 });
return (
<div>
...
<Child onSizeChange={setSize} />
...
</div>
);
};
function Child() {
const { ref: resizeRef, width, height } = useResizeDetector();
// Adding a useEffect here means that the state is owned by the wrong component
useEffect(() => {
onSizeChange({ width, height });
}, [width, height, onSizeChange]);
return <div ref={resizeRef}>...</div>;
} ✅ Better code function Parent() {
const { ref: resizeRef, width, height } = useResizeDetector();
return (
<div>
...
<Child ref={resizeRef} />
...
</div>
);
};
const Child = forwardRef(function Child(props, ref) {
return <div ref={ref}>...</div>;
}) If both child and parent want access to the size, the "proper" approach is probably for each of them to have their own instance of |
@tylerprice1 Thank you for engaging in this discussion! Your comments reveal your profound understanding of the subject. There is always a dilemma between performance and user-friendliness. Lifting the state, lifting the In situations where your application has been optimized to the extent that an additional re-render becomes a hindrance, you might consider rewriting the code using the native ResizeObserver API. Alternatively, you could create an additional component and encapsulate it within a "memo" function, as follows: // Before
function Parent() {
const { ref: resizeRef, width, height } = useResizeDetector();
return (
<div ref={ref}>
<div>
// ...
</div>
</div>
);
};
// After
const Child = React.memo(() => {
return (
<div>
// ...
</div>
);
});
function Parent() {
const { ref, width, height } = useResizeDetector();
return <div ref={ref}><Child height={height} /></div>;
}; In essence, my aim is to maintain the library's simplicity and ease of use. This allows new users to seamlessly integrate it into their existing codebases within a short time frame. I hope it clarifies my perspective on the library and explains why I am not in favor of lifting the state and removing the onResize callback. However, it doesn't prevent me from saying that your suggestions are very good for personal use! 👍 |
I believe there's a problem with this code:
Calling
onResize
inside ofsetState
(of the component where theuseResizer
hook is used) is producing a warning in case theonResize
handler in turn modifies internal state of another (for example parent's) component.In my case React throws this message into console:
I guess, simply moving this event call to some kind of
useEffect
, or usingPromise.resolve().then(() => onResize?.(width, height))
orsetTimeout(...)
would mitigate the issue. The idea is to make the call from a different event.The text was updated successfully, but these errors were encountered: