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

Bug: React.StrictMode breaks component that manage global object to break dependency between 2 related contexts #21930

Closed
Eliav2 opened this issue Jul 21, 2021 · 4 comments
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@Eliav2
Copy link

Eliav2 commented Jul 21, 2021

I've just released v2.0 of a react-xarrows it breaks on apps that are wrapped with React.StrictMode. the component works perfectly fine on trees that are not wrapped with React.StrictMode

React version: 17.0.0

Steps To Reproduce

simple demo

code sandbox

ready to use dev enviroment

it takes about a minute to set up. this env will link react-xarrows to the running example on port 300, so any changes on ./src would immediately be reflected.

Open in Gitpod

  1. after entering to gitpod, wait to setup.
  2. explore the demos, they work fine.
  3. the index file of create-react-app should be opened. if not go to ./examples/src/index.jsx .
  4. on render function of react-dom replace <ExamplePage /> with <React.StrictMode><ExamplePage /></React.StrictMode>
  5. refresh the page(in the mini gitpod browser on the right, not the main page).
  6. explore demos. The component does not behave the same!

I've tested the lifecycle of react-xarrows in both mods and it appears to be identical, so I can't figure out why apps that use strictmode breaks react-xarrows. I could not figure out why react-xarrows is not printed in StrictMode,

related and possible cause

  • react-arrows v2 manage global object of references to update functions to break the dependency between 2 related contexts. details, Xwrapper code.
    In simple words - I use a global object in the module Xwrapper.tsx, I pass a reference to this object to both contexts, then one context assigning 'update function'(a function that will cause xarrow to rerender) as a property and the second context consumes that function, and then elements connected to xarrow can selectively rerender only xarrow without triggering renders on other boxes.
    I thought that maybe the fact that on StrictMode each render becomes 2 renders causing this issue, but it does not seem to be the problem.

The current behavior

react-xarrows does not work properly on react-trees wrapped with React.StrictMode

The expected behavior

the behavior should be identical in both cases.

@Eliav2 Eliav2 added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Jul 21, 2021
@Eliav2 Eliav2 changed the title Bug: React.StrcitMode breaks my component Bug: React.StrcitMode breaks component that manage global object to break dependency between 2 related contexts Jul 21, 2021
@Eliav2 Eliav2 changed the title Bug: React.StrcitMode breaks component that manage global object to break dependency between 2 related contexts Bug: React.StrictMode breaks component that manage global object to break dependency between 2 related contexts Jul 21, 2021
@gaearon
Copy link
Collaborator

gaearon commented Jul 21, 2021

We disable console.log during second render pass so that's likely why you're not seeing it. (We'll change this behavior soon — no need to say it's confusing, we know :)

To work around, do

let log = console.log

and then use log(...) and you should see both logs.


As for your problem, I haven't looked in detail (there is no reason to suspect a bug in React so you're better off asking on StackOverflow). But you're clearly breaking the rules of React. In React, rendering is supposed to be a pure function — you should not be modifying external objects or relying on order of different components rendering. So the approach you're using with modifying a global object during render is fundamentally flawed, and you need to rethink it. StrictMode surfaced it but since you're breaking the rules, there are likely other "normal" cases without StrictMode that are also broken.

@gaearon gaearon closed this as completed Jul 21, 2021
@gaearon
Copy link
Collaborator

gaearon commented Jul 21, 2021

I would be able to give you a better answer if you made a sandbox with a much simplified version of your actual library code. No need for actual drawing of the arrow, but just enough to understand the data flow you wanted to have.

@Eliav2
Copy link
Author

Eliav2 commented Jul 24, 2021

Open in Gitpod

I've reproduced simple example with logs on special branch. you can see the second render reads the wrong position from dom and overwrite the correct value of the first render. this occurs only on StrictMode and does not even relate to the contexts and the global object I manage. This seems to be a bug (or limitation ) in StrictMode, so I would be grateful if you could explain where exactly I break any rule of react.

Note - the imported logs is the last ones (on mount) that showing the overite of the correct position with wrong one:
image

@gaearon

@Eliav2
Copy link
Author

Eliav2 commented Jul 24, 2021

after repetitive tests, I found out what exactly is the cause, and I still insist this is not expected, please read carefully:

First I will use the next 2 terms

  • update call - execution of the function body(no effects)
  • render cycle - update call and following effects

2 very important notes:

  • Calling state hook from effect(like useEffect or useLayoutEffect) will cause React to schedule another render
  • Calling state hook from FC body will cause React to schedule another update call
    for more details read my article on this repo

on StrictMode:
after small experiment,2 update calls,1 render for each render cycle(means the body of the FC executes twice while effect fire once)

code

see the next code (used in Xarrow.tsx), and lets remember react triggers 2 updates(at least) per render on StrictMode, if the first update will trigger another update(via setState for example) it will come before the second update strictmode schedules:

  if (shouldUpdatePosition.current) {
    ...
    const pos = getPosition(...)
    setSt(pos);
    log('pos',pos)
    shouldUpdatePosition.current = false;
  }
log('st',st)

on the first render shouldUpdatePosition.current=true so setSt gets the right position and update call scheduled by React with the right dimensions. the update call would execute and then on StrictMode, the second update would start(and the 3'th update call) and now shouldUpdatePosition.current=false the setSt is not executed, and React takes the value from the first update and not from the previous update call. this is unexpected!

recap, and logs:

for logs explanation, let's say 0 is the wrong(not updated) value and 10 is the correct value

  • update 0
    'pos',10 (set state trigger, another update call is scheduled)
    'st',0
  • update 1
    'st',10
  • update 2 (StrictMode only! the second update call of strict mode!)
    'st',0 (why? not expected! should be 10)

in order to fix this bug and make the result as expected react should take the value from the most recent update call and not the the first update call of the current cycle in strict mode

code sandbox

https://codesandbox.io/s/react-strict-mode-bug-bx8is?file=/src/index.js

edit

moved this to another issue #21956

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests

2 participants