meteor / react-packages

Meteor packages for a great React developer experience
http://guide.meteor.com/react.html
Other
574 stars 159 forks source link

Start tracker with useEffect vs useLayoutEffect #331

Closed crapthings closed 2 years ago

crapthings commented 3 years ago

what is different if we start tracker inside useEffect or useLayoutEffect?

i'm facing something like this

https://github.com/veliovgroup/flow-router/issues/88

so i've try to move tracker.autorun inside an useLayoutEffect, it looks the hook works like the old class component way.

https://reactjs.org/docs/hooks-reference.html#uselayouteffect

If you’re migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate.

make tracker.autorun inside useEffect, will cause rerun before unmounting, so i want to prevent this

CaptainN commented 3 years ago

useEffect and useLayoutEffect work the same with one key difference. useEffect yields after the first render, before useEffect is run to allow the browser to update/paint. useLayoutEffect does not yield, so that you can manipulate DOM elements.

Because of this difference, your are seeing that useLayoutEffect does appear to update the view immediately when the route changes, but in reality, it's just delaying rendering. It "appears" to work like the old withTracker but doesn't exactly - that used the deprecated componentWillMount method, for which there is no hooks alternative.

As for useTracker - it's true that it uses useEffect because we don't want to block render/paint by default. But we also mitigate the lag by 1 problem, specifically because of issues uncovered with routing (react-router during development, but same idea). Routers tend to reuse mounted components, instead of of forcing a full lifecycle, so we use some tricks in useTracker to avoid the problem. When deps change, it invalidates useMemo and recreates the computation. You could also use useTracker without deps, which uses a different algorithm internally, one which always runs the computation inline with render. So there's never a lag! withTracker uses the same internal algorithm (and always has!)

Alternatively, you could force a lifecylce change on route change by changing the key property of your route react element, which will force clear the useEffect. That will force the component to be rebuilt in sync with render even when using deps.

You could of course, roll your own useTracker alternative - using useLayoutEffect.

CaptainN commented 3 years ago

I wonder if folks would use a useLayoutTracker 🤔.

The problem is that might send the wrong signal. The truth is, renders should be cheap and disposable. Computations are also cheap and disposable. There's not really a problem that comes from double renders and the like, especially in concurrent mode, unless you measure a specific problem.

crapthings commented 3 years ago

yes the tracker and react render is very cheap.

the only problem i think is

image

switch between two component share same meteor tracker aware reactive source

Comp1 first load
Comp1 tracker start to run
Comp2 about to switch
Comp1 last tracker rerun with Comp2's reactive source // this is will break things
Comp2 tracker start to run
Comp1 stop tracker then unmount 
CaptainN commented 3 years ago

Yeah, that was a tricky problem we uncovered during the development of useTracker. We ended up churning a LOT to get a solution to that which made sense. There is no perfect way to do it, but you can use useMemo to make sure things change immediately when deps change (or roll your own deps compare), and then commit everything in useEffect. But then you get double renders. You can try to retain the effect produced in useMemo (like we used to do in useTracker - but it's super hard, and hacky, and not really "the react way."

Is there a reason you aren't using useTracker or withTracker?

filipenevola commented 2 years ago

I'm closing this issue because we haven't heard anything from the original poster for a while.