-
Notifications
You must be signed in to change notification settings - Fork 79
Use React's new Context API #406
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
Conversation
Went with using jest after all. Generation of the snapshot tests was super easy, I've opted for inline mode as that makes it easier to see the expected results (and easier to compare before/after). Renamed the files according to jest's convention as well. Unfortunately that led to git losing track of some 🙈 Taking another week-or-so break from this, but if the general Jest direction is alright, I'd continue with that. cc @stasm |
Oh, this is great!
I added the
I'm 100% for this change. Thank you! |
Also, sorry about the conflicts your branch has against master now 😔 I hope they're not too hard to resolve. |
Cool! :) No worries about the conflicts, that's what happens when I create monolithic PRs against an active project 😁 |
cf63fd4
to
55db0be
Compare
alrighty, synced it up with master and updated withLocalization (+ its tests) to use the new context API as well. I'm wondering if the Apart from that outstanding todos are:
|
I might need some help on modifiying |
I'd look into make and not bash for this, something like https://www.gnu.org/software/make/manual/html_node/Overriding-Makefiles.html sounds interesting. Or just try to overwrite |
I think Lines 22 to 23 in 4312437
Thinking about it, I probably got ahead of myself here. We only have 8 packages. It shouldn't be a lot of work to just move the |
I think it's fine to remove them. |
Cool! 👌 I made the makefile change, but I'm not the most experienced makefile-engineer, so maybe someone wants to double-check what I did there. Had a bit of a change of mind wrt including the hook with this change: If people have to upgrade to a React-hook version to use this anyway, maybe they should be rewarded with a hook. Wdyt? Or should I rewrite this to not use hooks anyway? My thinking is that an upgrade inside of React 16 is painless (no breaking changes), so we might as well skip < 16.8 |
fluent-react/src/localized.js
Outdated
const { l10n, parseMarkup } = useContext(FluentContext); | ||
|
||
if (l10n === null) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe log a warning and return child
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing an error here changes the current behavior, which is to return the child element as the fallback, as @dvdmgl says. IIRC the purpose of not throwing was to make it easier to test code which uses <Localized>
without having to set up a provider. I'm open to revisiting this but I think it would be safer to do it in a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see another check for !l10n
down below in line 70. I'd say this one up here is redundant then.
I'm back from my leave of absence, and I'd like to help land this PR. Thanks again to @Gregoor for submitting this PR and to @dvdmgl for the comments. @Gregoor Would you be available and would like to finish this PR?
I think the use of hooks in this PR is fine. I was wodering if it would make sense to use the Context API verbatim, and even let users wrap their apps in When you say rewarded with a hook, do you mean something like a |
Welcome back, Staś! Good to have you back, hope your time off was as great as it looked on Twitter :)
Huh, from reading my comment it seems that's what I meant but I can't say that I would still be able to argue for it 😁 |
@stasm I'm wondering what's left to be done here. From reading the comments, I think this was the only one outstanding from my side:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, this is very close, but I'd like to make a few small changes before merging. My comments are below.
fluent-react/src/localized.js
Outdated
const { l10n, parseMarkup } = useContext(FluentContext); | ||
|
||
if (l10n === null) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Throwing an error here changes the current behavior, which is to return the child element as the fallback, as @dvdmgl says. IIRC the purpose of not throwing was to make it easier to test code which uses <Localized>
without having to set up a provider. I'm open to revisiting this but I think it would be safer to do it in a separate PR.
fluent-react/src/localized.js
Outdated
const { l10n, parseMarkup } = useContext(FluentContext); | ||
|
||
if (l10n === null) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see another check for !l10n
down below in line 70. I'd say this one up here is redundant then.
@@ -0,0 +1,15 @@ | |||
module.exports = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file required for running tests with Jest? Please add a one-line comment explaining why we need it :)
fluent-react/src/provider.js
Outdated
} | ||
|
||
componentWillReceiveProps(next) { | ||
const { bundles } = next; | ||
const bundles = useMemo(() => CachedSyncIterable.from(props.bundles), [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned about the use of useMemo
here. To quote the docs:
You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.
I'd prefer to use an API with stronger guarantees here, e.g. useEffect
. Or, as you suggested on Slack, refactor this to memoize the entire context object.
fluent-react/src/provider.js
Outdated
]); | ||
const contextValue = useMemo( | ||
() => { | ||
const l10n = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I preferred it when this was a separate ReactLocalization
class in its own file. What do you think? I see that right now you're relying on a closure to have access to bundles
; I'd OK with re-creating the instance of ReactLocalization
when the props to the provider change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be in line with your proposal from Slack to memoize the entire context rather than individual props, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to (and now did) memoize the whole Provider component and not just the contextValue. That should make things a lot clearer. I also added a test to make sure that it stop unnecessary re-renders. It doesn't have the drawbacks of useMemo
, namely
- no extra per-render array+function-scope allocations
- (probably) no future react might forget your memoization (at least not according to the docs)
A memoized (aka pure) component does an additional thing which a non-memoized Component doesn't do: shallowly compare props. But that's cheap enough and seems worth it, compared to (re)iterating the bundles.
Wrt the ReactLocalization class: I already had a comment typed out protesting against it, but then I gave myself a real think about it and ended up agreeing and bringing it back. Apparently I also removed ReactLocalization's comments in the process without finding a better place for them. Shame on me!
For posterity reasons why I think bringing it back is a good idea:
- JS engines do a better job at optimizing for classes with known methods and fields
- the concerns of the Provider and the Localization are separate enough
- context is initialized with the same type as it's being used with (which I failed to do before)
And for completeness-sake a reason against it would've been that it moves related code further apart, making it potentially a little harder to grok and edit.
Thanks for the thorough review!
) | ||
); | ||
} | ||
function WithDisplay(props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for this rename?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly have no clue what happened there. Looks like an accident, I reverted it. Thanks for catching it!
It simplifies the code a lot and might also make it faster, as memoization during render comes at the cost of additional array and function scope allocation. This also adds a test to check that the Provider is not re-rendered when the bundles prop does not change.
Co-Authored-By: Staś Małolepszy <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this looks great. I have one more comment and a question, and we're done :)
- I'd prefer to not add new Babel plugins, in this case
@babel/plugin-proposal-class-properties
. I'm on a quest to simplify the build setup across all Fluent packages. I hope to be able to remove@babel/plugin-proposal-async-generator-functions
, too, when we drop Node.js 8 support in Require Node.js 10 #341. - I have a question below about what the value of the context should be. Right now it's
{l10n, parseMarkup}
, but I wouldn't mind movingparseMarkup
intoReactLocalization
, similar toreportError
, and using the instance ofReactLocalization
as the context value. What do you think?
FluentContext.Provider, | ||
{ value: { | ||
l10n: new ReactLocalization(props.bundles, props.parseMarkup), | ||
parseMarkup: props.parseMarkup || createParseMarkup() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it stands right now, the ReactLocalization
constructor doesn't take parseMarkup
as an argument. That said, I wonder if it would actually be a good idea to make the value of the context be the instance of ReactLocalization
, rather than the {l10n, parseMarkup}
object. Then, passing parseMarkup
to the constructor would be justified. @Gregoor, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this one yet, as I don't see a semantic reason for them to live together. Are there other ergonomic/performance considerations that might make us want to do that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm mostly basing this on how this works in @fluent/dom
. There we have the DOMLocalization
class which has all the logic for localizing DOM elements. I'd lean towards considering parseMarkup
part of ReactLocalization
, too.
In longer term, I expect parseMakrup
to be removed as a configurable property, which would leave l10n
as the only property of the context value. I don't see many benefits to keeping this extra level of nesting, but perhaps it's because I generally tend to prefer flatter structures :)
8d563cb
to
0a83a19
Compare
2fe7e40
to
d02a9c5
Compare
I'd like to change the value of the context to be the instance of |
I remember typing a reply before the holidays and having some thoughts about this (agreeing with you), but I didn't seem to have send it 🤔 I'm happy either way! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, thanks. Let's land this as-is, and I'll file a follow-up about the context value.
After talking with Stas it sounded like all of our internal projects are using a recent enough version of React to allow us to switch from the legacy to the new context API. My motivation is to add hooks to fluent. That'd follow in a later PR.
Functionality wise everything should be there for
Localized
, working at fixing the tests for it next. After that I'll look atwithLocalization
.This would also include an API change, namely not exporting
ReactLocalization
anymore. With hooks it was possible to merge the same functionality intoLocalizationProvider
without it getting noisy (at least not to me)