As far as I know, there are three ways to define functions in JavaScript.
1. Declaration
function handleEvent(e) {}
2. Assignment
var handleEvent = function(e) {}
3. Arrow
var handleEvent = (e) => {}
I have been searching for hours trying to find information on which of these options is the preferred way to declare handlers in functional React components. All the articles I found talk about class components, binding, etc. But with the new Hooks out, there must be standard for defining them inside the function as well. (After all, functional components have always existed anyway.)
Consider the following component, which declares three handlers, which are examples of different behaviour that you might need in a React component.
function NameForm(props) {
const [inputName, setInputName] = useState("");
useEffect(() => setInputName(props.initialValue), [props.initialValue]);
const handleInputChange = function(event) {
setInputName(event.target.value);
};
const resetForm = function() {
setInputName(props.initialValue);
props.onReset();
};
const handleFormSubmit = function(event) {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
};
/* React-Bootstrap simple form example using these handlers. */
return (
<Form onSubmit={handleFormSubmit}>
<Form.Group controlId="NameForm">
<Form.Control
type="text"
placeholder="Enter your name here"
value={inputName}
onChange={handleInputChange}
/>
<Button type="submit">Submit</Button>
<Button onClick={resetForm}>Reset</Button>
</Form.Group>
</Form>
);
}
All of these handlers are directly passed as callbacks into other components. They might be called whenever, but at that exact moment, we need to have access to the current values of props
and any state like inputName
. Additionally, as you might have noticed, the handleFormSubmit
handler also calls the resetForm
handler.
What would be the recommended approach to defining the handlers from a performance perspective? Can it be avoidable that they are redefined on every render?
Does useCallback
also fit in here somewhere?
The current standard is to declare the handlers as constants for immutability and as arrow-functions for binding purposes.
function NameForm(props) {
const [inputName, setInputName] = useState("");
useEffect(() => setInputName(props.initialValue), [props.initialValue]);
const handleInputChange = (event) => {
setInputName(event.target.value);
}
const resetForm = () => {
setInputName(props.initialValue);
props.onReset();
}
const handleFormSubmit = (event) => {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
}
/* React-Bootstrap simple form example using these handlers. */
return (
<Form onSubmit={handleFormSubmit}>
<Form.Group controlId="NameForm">
<Form.Control
type="text"
placeholder="Enter your name here"
value={inputName}
onChange={handleInputChange}
/>
<Button type="submit">Submit</Button>
<Button onClick={resetForm}>Reset</Button>
</Form.Group>
</Form>
);
}
All of these handlers are directly passed as callbacks into other components. They might be called whenever, but at that exact moment, we need to have access to the current values of props and any state like inputName
As currently constructed we meet the requirements for your description. Since props
and state
are defined as higher level data that the component has access to, all the event-handlers have access to them. And when used as a call-back in another component, they will remain bound to the initial component where they were defined in.
Which means if you have an event-handler like this:
const handleInputChange = (e) => {
setValue(e.target.value)
}
And you pass it into a ChildComponent like this:
<Child handleInputChange={handleInputChange}/>
And the Child uses the prop/event-handler like this:
<input onChange={props.handleInputChange}/>
You would be getting the event
from the Child input, but you would be updating the state
of the Parent, the original component that defined the event-handler.
There is virtually no difference between the "declaration", "assignment" and "arrow" approaches. The only thing that matters is that you don't always create new instances of the handler functions on each render. For this, use the useCallback
hook to memoize the function references:
const handleInputChange = useCallback((event) => {
setInputName(event.target.value);
}, []); // `setInputName` is guaranteed to be unique, from the React Hooks docs, so no need to pass it as a dependency
const resetForm = useCallback(() => {
setInputName(props.initialValue);
props.onReset();
}, [props.onReset, props.initialValue]; // these come from props, so we don't know if they're unique => need to be passed as dependencies
const handleFormSubmit = useCallback((event) => {
event.preventDefault();
props.onSubmit(inputName);
resetForm();
}, [props.onSubmit, resetForm, inputName]); // `resetForm` and `inputName`, although defined locally, will change between renders, so we also need to pass them as dependencies
useCallback
docs: https://reactjs.org/docs/hooks-reference.html#usecallback
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With