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

Use custom hook to create callback constants #933

Merged
merged 2 commits into from
Jul 23, 2021
Merged

Conversation

erikras
Copy link
Member

@erikras erikras commented Jul 23, 2021

Further abstraction around #931.

@erikras erikras self-assigned this Jul 23, 2021
@codesandbox-ci
Copy link

codesandbox-ci bot commented Jul 23, 2021

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit d77bbbf:

Sandbox Source
React Final Form - Simple Example Configuration
React Final Form - Synchronous Record Level Validation Configuration
React Final Form - Synchronous Field Level Validation Configuration
React Final Form - Submission Errors Configuration
React Final Form - Subscriptions Example Configuration

@erikras erikras force-pushed the callbacks-as-constants branch from 9d2de52 to c34f8ee Compare July 23, 2021 08:16
@erikras erikras force-pushed the callbacks-as-constants branch from c34f8ee to af1c2cf Compare July 23, 2021 08:17
src/useField.js Outdated
state.change(format(fieldState.value, state.name))
}
},
[form, format, formatOnBlur, state]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided in the same order as line 172.

src/useField.js Outdated
: event
state.change(parse(value, name))
},
[component, name, parse, state, type, _value]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided in the same order as line 191.

Comment on lines 10 to 12
const refs = deps.map(React.useRef)
// update refs on each additional render
deps.forEach((dep, index) => (refs[index].current = dep))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't refs get created at each render, this forEach line is not needed 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they don't. useRef is similar to useState -- whatever is passed as it's first param only initialises the state/ref in the very first render.

Copy link

@afzalsayed96 afzalsayed96 Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erikras My understanding is that you shouldn't update refs in render function as it would break with Suspense. See Dan's comment on this issue: facebook/react#21191 (comment)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, even if the deps.map is called each time, it will still get a new array but with first deps

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he probably means why not a single ref containing the deps array, instead of a ref for each dep. Then you don't need the forEach line, or the length check.

Copy link
Member Author

@erikras erikras Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he probably means why not a single ref containing the deps array, instead of a ref for each dep. Then you don't need the forEach line, or the length check.

I wrote this yesterday, and had this thought upon waking this morning. Why not save the whole array? 👍

*/
export default function useConstantCallback(callback, deps) {
// initialize refs on first render
const refs = deps.map(React.useRef)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to add this line as a safety net for sanity

if (deps.length !== refs.length) {
  console.warn("Length of `deps` shouldn't change between renders")
}

* Creates a callback, with dependencies, that will be
* instance === for the lifetime of the component.
*/
export default function useConstantCallback(callback, deps) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would something like the following help? It will always return the same instance for === purposes, but will call
the latest function passed in. No need to care about callback dependencies at all:

export function useWrappedCallback(callback) {
	const ref = React.useRef(callback);
	React.useEffect(() => { ref.current = callback; });
	return React.useCallback((...args) => ref.current.apply(null, args), []);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh!! I like this a lot!

Copy link
Contributor

@kapral18 kapral18 Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is epic. I had to stare at it for some time tho. So basically we always return the same identity which is inline with earlier version. BUT we work with inner function which is passed from render which is up to date on every render, thus no need for deps at all indeed and also this scales perfectly, i.e. over time when new vars are added or removed we have to change NOTHING! If I knew about this smart pattern I'd use this instead in previous PR. So I am all for it. (cc: @erikras )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated PR with your implementation. MUCH nicer!

@erikras erikras force-pushed the callbacks-as-constants branch from 2782fd2 to d77bbbf Compare July 23, 2021 12:07
@erikras erikras merged commit 916db00 into main Jul 23, 2021
@erikras erikras deleted the callbacks-as-constants branch July 23, 2021 12:51
@erikras
Copy link
Member Author

erikras commented Sep 29, 2021

Published in v6.5.4.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants