Page MenuHomePhabricator

[web] [refactor] create modal context, use for setModal
ClosedPublic

Authored by benschac on Feb 16 2022, 5:24 AM.
Tags
None
Referenced Files
Unknown Object (File)
Fri, Nov 8, 10:42 AM
Unknown Object (File)
Sat, Nov 2, 6:37 AM
Unknown Object (File)
Sat, Nov 2, 6:37 AM
Unknown Object (File)
Sat, Nov 2, 6:37 AM
Unknown Object (File)
Sat, Nov 2, 6:37 AM
Unknown Object (File)
Sat, Nov 2, 6:36 AM
Unknown Object (File)
Sat, Nov 2, 6:36 AM
Unknown Object (File)
Sat, Nov 2, 6:36 AM

Details

Summary

adds modal context to be used instead of internal state so we don't have to prop drill

Depends On: D3214

Test Plan

n/a this doesn't do anything yet.

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

benschac retitled this revision from [web] [refactor] create modal context to [web] [refactor] create modal context, use for setModal.Feb 16 2022, 5:26 AM

Realizing now this could be two diffs. It's pretty short in general. I can break it up if need be.

ashoat requested changes to this revision.Feb 21 2022, 9:48 PM
ashoat added inline comments.
web/modals/modal-provider.react.js
3 ↗(On Diff #9718)

Nit: please keep import lines separate from type lines (add a newline here). Hoping this isn't Prettier's fault, but I suspect it's not because I'm guessing the Flow types get stripped before Prettier sees them. I'm guessing Prettier doesn't make this worse but it probably can't fix it either

24–28 ↗(On Diff #9718)

It makes no sense that you put setModal in one of these dep lists but not the other.

High-level feedback: try to be more rigorous in how you think about dep lists... they're one of the things you really have to understand 100% to be able to write good React hook code.

More concrete feedback: useState setters never change, so you can treat them just like constants and leave them out of dep lists. That's why eslint-plugin-react-hooks doesn't complain when they're left off of dep lists.

26 ↗(On Diff #9718)

Based on my read of the types here, it seems like you can just pass setModal in directly. Did you do it this way on purpose? I guess an argument can be made that it's more readable this way

26 ↗(On Diff #9718)

handleClick is a bad name here... setModal can be called from contexts that have nothing to do with a click being handled. Can you come up with a better name? Even something generic like setModalAPI or contextSetModal would be better, I think

31 ↗(On Diff #9718)

By passing the object directly in like this, you make both of the React.useCallback calls above completely useless.

In other words: if you had written the exact same code without the React.useCallbacks, the performance would be no worse (in fact, exactly the same). That's because the React.useCallbacks you've implemented fail to stop any rerenders, since they're wrapped in an object that is created anew on every render.

The whole point of React.useMemo, React.useCallback, and dep lists is to reduce unnecessary render cycles. The thinking goes: by memoizing props passed to child components, you make it possible for the child components (if they implement React.PureComponent or use React.memo†) to avoid rerendering whenever the parent rerenders.

This is generally good to keep in mind anywhere, but particularly important when writing a Provider component. By failing to memoize the prop here, you force the children of ModalProvider to have to rerender whenever either ModalProvider rerenders, or an ancestor of ModalProvider rerenders.‡

As it turns out, the latter (ancestor of ModalProvider rerendering) never happens because all of its ancestors are pure components. And as for ModalProvider rerendering, this only happens when setModal or clearModal are called.

By wrapping this object instantiation in a React.useMemo, you may actually achieve a perf benefit when setModal and clearModal are called. In that situation we might avoid rerendering HotRoot. To understand if a perf benefit exists, you would have to understand how react-hot-loader works in prod (this was pretty easy for me) and how react-router works (I didn't manage to figure this out in 10min so gave up).

Given this is a Provider component near the root of the component hierarchy, it's probably safest just to wrap this object instantiation in a React.useMemo.

Try to keep this sort of thing in mind going forward. A good rule of thumb is to be suspicious whenever you see {{ }}.

† Note that this refers to React.memo, not React.useMemo. They are in fact different.
‡ If you're curious to learn more, I wrote a note about this while reviewing this diff.

web/root.js
25 ↗(On Diff #9718)

I'm concerned that you're wrapping HotRoot here. react-hot-loader tends to have a lot of opinions about this stuff, and the docs indicate you should be wrapping the root component of your app.

Instead of putting ModalProvider here, I think we should probably put it in the App component (the one inside app.react.js).

(I vaguely recall there was some good reason react-redux's Provider has to go even higher than react-hot-loader, but I don't recall exactly what that reason was. Don't think we should change that.)

This revision now requires changes to proceed.Feb 21 2022, 9:48 PM

Please read the above comments thoroughly, and consult @atul if anything is confusing! It took me a long time to type everything up, so I'm hoping we can get this diff updated / landed without too much more engagement from me :)

diff comments + move modal Context to hook

remove Modal Context from export

remove function that I shouldn't have included

web/app.react.js
202 ↗(On Diff #9845)

Assuming it's okay to remove this useMemo since I use one in the wrapper?

web/modals/modal-provider.react.js
46 ↗(On Diff #9843)

Just took another look at the code with a fresh set of eyes.

I think throwing this into a hook is the _cleanest_ solution. We don't have to import a bunch of invariants and wrap the context in a useContext

3 ↗(On Diff #9718)

I just don't think there's an eslint rule and prettier doesn't pick up on it either.

26 ↗(On Diff #9718)

I'm not 100% if you mean just this function or the API for this provider.

web/modals/modal-provider.react.js
26 ↗(On Diff #9718)

talked IRL. Just changing handleClick here

ashoat requested changes to this revision.Feb 23 2022, 11:53 AM

This is basically good now, just two comments inline

web/app.react.js
202 ↗(On Diff #9845)

Yeah, totally okay

web/modals/modal-provider.react.js
27–29 ↗(On Diff #9845)

I think I never got a response to this question:

Based on my read of the types here, it seems like you can just pass setModal in directly. Did you do it this way on purpose? I guess an argument can be made that it's more readable this way

What if you never declared contextSetModal, and just passed in setModal itself directly. Would that work?

31–37 ↗(On Diff #9845)

Nit: there's a shorthand for this

This revision now requires changes to proceed.Feb 23 2022, 11:53 AM

[web] [refactor] squashed diff of 30+ diff refactor, setModal to context

I meant to put the squashed commit in a new diff.

ignore this for now.

remove setState from clearModal dep list

web/modals/modal-provider.react.js
26 ↗(On Diff #9718)

Based on my read of the types here, it seems like you can just pass setModal in directly. Did you do it this way on purpose? I guess an argument can be made that it's more readable this way

I thought it was more readable. I can change it though.

ashoat requested changes to this revision.Feb 24 2022, 12:25 PM
ashoat added inline comments.
web/modals/modal-provider.react.js
28–30 ↗(On Diff #9909)

This seems like an alias for setModal, not clear why it needs to exist

32–38 ↗(On Diff #9909)

We can use a shorthand to avoid the return keyword

This revision now requires changes to proceed.Feb 24 2022, 12:25 PM

remove useCallback for setModal

ashoat added inline comments.
web/modals/modal-provider.react.js
47 ↗(On Diff #9913)

Nit: can you add a newline before the export line?

This revision is now accepted and ready to land.Feb 24 2022, 1:06 PM
This revision was landed with ongoing or failed builds.Feb 24 2022, 1:23 PM
This revision was automatically updated to reflect the committed changes.