Correct way to define handlers in functional React components? - javascript

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

In functional component we don't need access to this(even more - most linters will give you warning in such a case - and for a reason!). So it does not matter if we use arrow expression or declare a function.
But performance matters. Whatever option from your list you choose it will be recreated on each render. Declaring function itself is not really big deal but:
passed down to child it may cause unnecessary re-rendering for PureComponent/React.memo-wrapped components.
May cause redundant run for useMemo/other useCallback/useEffect if later one includes your handler into dependencies list.
So either declare handler out of the component(once it does not rely on internal state at all) or use useCallback. Beware it needs you explicitly list of all dependencies - don't ignore that. Otherwise result handler may operate on stale data.

Related

Why include external functions in dependency array in useEffect [duplicate]

I'm having a hard time understanding the 'exhaustive-deps' lint rule.
I already read this post and this post but I could not find an answer.
Here is a simple React component with the lint issue:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
useEffect(() => {
onChange(value);
}, [value]);
return (
<input
value={value}
type='text'
onChange={(event) => setValue(event.target.value)}>
</input>
)
}
It requires me to add onChange to the useEffect dependencies array. But in my understanding onChange will never change, so it should not be there.
Usually I manage it like this:
const MyCustomComponent = ({onChange}) => {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
onChange(event.target.value)
}
return (
<input
value={value}
type='text'
onChange={handleChange}>
</input> ​
)
}
Why the lint? Any clear explanation about the lint rule for the first example?
Or should I not be using useEffect here? (I'm a noob with hooks)
The reason the linter rule wants onChange to go into the useEffect hook is because it's possible for onChange to change between renders, and the lint rule is intended to prevent that sort of "stale data" reference.
For example:
const MyParentComponent = () => {
const onChange = (value) => { console.log(value); }
return <MyCustomComponent onChange={onChange} />
}
Every single render of MyParentComponent will pass a different onChange function to MyCustomComponent.
In your specific case, you probably don't care: you only want to call onChange when the value changes, not when the onChange function changes. However, that's not clear from how you're using useEffect.
The root here is that your useEffect is somewhat unidiomatic.
useEffect is best used for side-effects, but here you're using it as a sort of "subscription" concept, like: "do X when Y changes". That does sort of work functionally, due to the mechanics of the deps array, (though in this case you're also calling onChange on initial render, which is probably unwanted), but it's not the intended purpose.
Calling onChange really isn't a side-effect here, it's just an effect of triggering the onChange event for <input>. So I do think your second version that calls both onChange and setValue together is more idiomatic.
If there were other ways of setting the value (e.g. a clear button), constantly having to remember to call onChange might be tedious, so I might write this as:
const MyCustomComponent = ({onChange}) => {
const [value, _setValue] = useState('');
// Always call onChange when we set the new value
const setValue = (newVal) => {
onChange(newVal);
_setValue(newVal);
}
return (
<input value={value} type='text' onChange={e => setValue(e.target.value)}></input>
<button onClick={() => setValue("")}>Clear</button>
)
}
But at this point this is hair-splitting.
The main purpose of the exhaustive-deps warning is to prevent the developers from missing dependencies inside their effect and lost some behavior.
Dan abramov – developer on Facebook core – strongly recommend to keep that rule enabled.
For the case of passing functions as dependencies, there is a dedicated chapter in the React FAQ:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
tl;dr
If you have to put a function inside your dependencies array:
Put the function outside of your component, so you are sure that the reference won't be changed on each render.
If you can, call the function outside of your effect, and just use the result as dependency.
If the function must be declared in your component scope, you have to memoize the function reference by using the useCallback hook. The reference will be changed only if the dependencies of the callback function change.

Using Variable from Props vs Passing Prop as Argument in React

In terms of writing components, which would be the preferred way to write below component? Assume that removeCard is outside of shown scope, ie. redux action.
My assumption would be that ComponentCardB would be, as it avoids passing an unnecessary argument which would be in the scope anyway. I imagine in terms of performance in the grand scheme of things, the difference is negligible, just more of a query in regards to best practise.
TIA
const ComponentCardA = (id) => {
const handleRemove = (cardId) => {
removeCard(cardId);
};
<div onClick={() => handleRemove(id)} />;
};
const ComponentCardB = (id) => {
const handleRemove = () => {
removeCard(id);
};
<div onClick={handleRemove} />;
};
With functional components like that, yes, there's no reason for the extra layer of indirection in ComponentCardA vs ComponentCardB.
Slightly tangential, but related: Depending on what you're passing handleRemove to and whether your component has other props or state, you may want to memoize handleRemove via useCallback or useMemo. The reason is that if other props or state change, your component function will get called again and (with your existing code) will create a new handleRemove function and pass that to the child. That means that the child has to be updated or re-rendered. If the change was unrelated to id, that update/rerender is unnecessary.
But if the component just has id and no other props, there's no point, and if it's just passing it to an HTML element (as opposed to React component), there's also probably no point as updating that element's click handler is a very efficient operation.
The second option is better way because using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
Also if you don't want to use syntax with props.id you rather create function component with object as parameter:
const Component = ({id}) => { /* ... */ }
Of course using arrow function is also allowed but remember, when you don't have to use them then don't.

creating a Boolean flag for onclick method

I am having some OOP issues that are probably pretty simple. I have a class that renders some html. However it has an onClick that calls a function that sets a flag inside the class if the image is clicked. Now here is the issue, when I render this class object and click the button from a separate js file, it stays false. I want it to permanently change the flag to true when clicked. here is the class...
class Settings extends React.Component {
handleClick() {
this.flag = true;
console.log(this.flag)
}
render(){
return(
<img src="./img/leaf.png" alt="" onClick={() => this.handleClick()}/>
);
}
}
and here is the code that calls it from a separate file...
const settingsObj = new Settings();
console.log(settingsObj.flag);
I want the flag to be false until the button is clecked and then it permamently changes to true. But it only goes true until my page rerenders as new data comes in and it resets to false. I have tried constructors and a few other techniques with no success.
Normal OOP design principles don't always apply directly to React components. Components don't usually have instance properties, they mostly just have props and state (there are a few exceptions where you do use an instance property, like Animation objects in react-native, but these are rare).
You're kind of mixing the two things in a way that doesn't quite make sense here. Settings is a React component that renders an image, but it's also an object which you instantiate by calling new Settings(). If there are other components which depend on the value of flag, you might want to separate the accessing and storing of the flag from the render component, passing a value and a callback to the renderer.
const Settings = ({setFlag}) => {
return(
<img src="./img/leaf.png" alt="" onClick={() => setFlag(true)}/>
);
}
You've suggested that you like the Context API as a solution for making the flag value globally available. There are a few ways to set this up, but here's one.
Outside of any component, we create a FlagContext object that has two properties: a boolean value flag and callback function setFlag. We need to give it a default fallback value, which is hopefully never used, so our default callback just logs a warning and does nothing.
const FlagContext = createContext<FlagContextState>({
flag: false,
setFlag: () => console.warn("attempted to use FlagContext outside of a valid provider")
});
This FlagContext object gives up Provider and Consumer components, but it's up to us to give a value to the FlagContext.Provider. So we'll create a custom component that handles that part. Our custom FlagProvider uses a local state to create and pass down the value. I've used a function component, but you could use a class component as well.
const FlagProvider = ({children}) => {
const [flag, setFlag] = useState(false);
return (
<FlagContext.Provider value={{
flag,
setFlag
}}>
{children}
</FlagContext.Provider>
)
}
We want to put the entire App inside of the FlagProvider so that the whole app has the potential to access flag and setFlag, and the whole app gets the same flag value.
When you want to use the value from the context in a component, you use either the useContext hook or the Consumer component. Either way, I like to creating an aliased name and export that rather than exporting the FlagContext object directly.
export const FlagConsumer = FlagContext.Consumer;
export const useFlagContext = () => useContext(FlagContext);
With the Consumer, the child of the consumer is a function that takes the value of the context, which in out case is an object with properties flag and setFlag, and returns some JSX.
This is usually a function you define inline:
const SomePage = () => {
return (
<FlagConsumer>
{({flag, setFlag}) => (<div>Flag Value is {flag.toString()}</div>)}
</FlagConsumer>
)
}
But it can also be a function component. Note that when using a function component as the child, you must pass the component itself ({Settings}) rather than an executed version of it (<Settings />).
const Settings = ({ setFlag }) => {
return <img src="./img/leaf.png" alt="" onClick={() => setFlag(true)} />;
};
const SomePage = () => {
return <FlagConsumer>{Settings}</FlagConsumer>;
};
The preferred method nowadays is with hooks. We call useFlagContext() inside the body of the function component and it returns our context object.
const SomePage = () => {
const {flag, setFlag} = useFlagContext();
return <Settings setFlag={setFlag}/>
};
Both the consumer and the hook only work if they are inside of a flag context provider, so that's why we put it around the whole app!
const App = () => {
return (
<FlagProvider>
<SomePage />
</FlagProvider>
);
};
Complete example on CodeSandbox
For this kind of interactions, I highly recommend you to use Redux
Another think I'm sure you will benefit from, is switching to hooks and function components: less boilerplate and much flexible code.
Back to the goal, using Redux your code would look similar to this:
const Settings = (props) => {
const dispatch = useDispatch();
const flag = useSelector(state => state.yourStoreObj.flag);
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<img src="./img/leaf.png" alt="" onClick={() => handleClick()}/>
);
}
Explanation:
First of all, spend 15 mins and get used to React Redux. Here's a good practical article to start with. If you're not familiar with hooks, start learning them as that will change a lot, while you don't need to change a single line of what you've done so far.
We suppose there's a property in the store that is the "flag" property of that specific element. In this way, the property can be read by the component itself with the useSelector() operator, or can be read anywhere in your application with the same methodology from any other component.
In the same way, you can change the value by dispatching a change (see dispatch() function) and in the same way, you can do that from any other components.
So, let's say you want to change that property when a click occurs on a completely different component, this is how the other component may looks like
const OtherCoolComp = (props) => {
const dispatch = useDispatch();
handleClick() {
dispatch(yourCustomAction("UPDATE_FLAG", true));
}
return(
<button onClick={() => handleClick()}>
Click me!
</button>
);
}
So you're dispatching the same action, setting it to the value you prefer, from a component that doesn't know who is displaying that value.

Event listener functions changing when using React hooks

I have a component that uses event listeners in several places through addEventListener and removeEventListener. It's not sufficient to use component methods like onMouseMove because I need to detect events outside the component as well.
I use hooks in the component, several of which have the dependency-array at the end, in particular useCallback(eventFunction, dependencies) with the event functions to be used with the listeners. The dependencies are typically stateful variables declared using useState.
From what I can tell, the identity of the function is significant in add/remove EventListener, so that if the function changes in between it doesn't work. At first i tried managing the hooks so that the event functions didn't change identity between add and remove but that quickly became unwieldy with the functions' dependency on state.
So in the end I came up with the following pattern: Since the setter-function (the second input parameter to useState) gets the current state as an argument, I can have event functions that never change after first render (do we still call this mount?) but still have access to up-to-date stateful variables. An example:
import React, { useCallback, useEffect, useState } from 'react';
const Component = () => {
const [state, setState] = useState(null);
const handleMouseMove = useCallback(() => {
setState((currentState) => {
// ... do something that involves currentState ...
return currentState;
});
}, []);
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, [/* ... some parameters here ... */]);
// ... more effects etc ...
return <span>test</span>;
};
(This is a much simplified illustration).
This seems to work fine, but I'm not sure if it feels quite right - using a setter function that never changes the state but just as a hack to access the current state.
Also, for event functions that require several state variables I have to nest the setter calls.
Is there another pattern that could handle this situation in a nicer way?
From what I can tell, the identity of the function is significant in add/remove EventListener, so that if the function changes in between it doesn't work.
While this is true, we do not have to go the extreme of arranging for even function not to change identity at all.
Simple steps will be:
Declare event function using useCallback - dependency list of useCallback should include all the stateful variables that your function depends on.
Use useEffect to add the event listener. Return cleanup function that will remove the event listener. Dependency list of useEffect should include the event listener function itself, in addition to any other stateful variable your effect function might be using.
This way, when any of stateful variable used by event listener changes, even listener's identity changes, which will trigger running of the effect, but before running the effect cleanup function returned by previous run of the effect will be run, properly removing old event listener before adding the new one.
Something on the lines of:
const Component = () => {
const [state, setState] = useState();
const eventListner = useCallback(() => {
console.log(state); // use the stateful variable in event listener
}, [state]);
useEffect(() => {
el.addEventListner('someEvent', eventListner);
return () => el.removeEventListener('someEvent', eventListner);
}, [eventListener]);
}
#ckedar 's solution can solve this question, but it has performance problem, when the eventListener change, react will remove and addEvent on the dom。
you can use useRef() instead useState(),if you want listen state change, you can use useStateRef():
import React, { useEffect, useRef, useState } from 'react';
export default function useStateRef(initialValue:any): Array<any>{
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
},[value])
return [value,setValue,ref];
}

Correct way to create event handlers using hooks in React?

In a typical class-based React component, this is how I would create an event handler:
class MyComponent extends Component {
handleClick = () => {
...
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
However, I find myself with two options when I use a hooks-based functional paradigm:
const MyComponent = () => {
const [handleClick] = useState(() => () => {
...
});
return <button onClick={handleClick}>Click Me</button>;
};
or alternatively:
const MyComponent = () => {
const handleClick = useRef(() => {
...
});
return <button onClick={handleClick.current}>Click Me</button>;
};
Which one is objectively better, and for what reason? Is there another (better) way that I have not yet heard of nor discovered?
Thank you for your help.
Edit: I have put an example here on CodeSandbox showing both methods. Neither seems to unnecessarily recreate the event handler on each render, as you can see from the code on there, so a possible performance issue is out of the question, I think.
I wouldn't recommend either useState or useRef.
You don't actually need any hook here at all. In many cases, I'd recommend simply doing this:
const MyComponent = () => {
const handleClick = (e) => {
//...
}
return <button onClick={handleClick}>Click Me</button>;
};
However, it's sometimes suggested to avoid declaring functions inside a render function (e.g. the jsx-no-lambda tslint rule). There's two reasons for this:
As a performance optimization to avoid declaring unnecessary functions.
To avoid unnecessary re-renders of pure components.
I wouldn't worry much about the first point: hooks are going to declare functions inside of functions, and it's not likely that that cost is going to be a major factor in your apps performance.
But the second point is sometimes valid: if a component is optimized (e.g. using React.memo or by being defined as a PureComponent) so that it only re-renders when provided new props, passing a new function instance may cause the component to re-render unnecessarily.
To handle this, React provides the useCallback hook, for memoizing callbacks:
const MyComponent = () => {
const handleClick = useCallback((e) => {
//...
}, [/* deps */])
return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};
useCallback will only return a new function when necessary (whenever a value in the deps array changes), so OptimizedButtonComponent won't re-render more than necessary. So this addresses issue #2. (Note that it doesn't address issue #1, every time we render, a new function is still created and passed to useCallback)
But I'd only do this where necessary. You could wrap every callback in useCallback, and it would work... but in most cases, it doesn't help anything: your original example with <button> won't benefit from a memoized callback, since <button> isn't an optimized component.

Categories