React when to use a Function in Props? - javascript

In React,
when to use a function in Props?
For example in the code below sometimes you don't use a function in prop,
but sometimes you do.
import React, { useState } from 'react';
const IterationSample = () => {
const input = React.createRef();
const [names, setNames] = useState(
[
{ id: 1, text: 'John' },
{ id: 2, text: 'Jake' },
{ id: 3, text: 'JJ' },
{ id: 4, text: 'Jesus' }
])
const [inputText, setInputText] = useState('');
const [nextId, setNextId] = useState(5);
const onChange = e => setInputText(e.target.value);
const onRemove = id => {
const nextNames = names.filter(name => name.id !== id);
setNames(nextNames);
}
const namesList = names.map(name => <li key={name.id} onDoubleClick={() => onRemove(name.id)}>{name.text}</li>);
const onClick = () => {
const nextNames = names.concat({
id: nextId,
text: inputText
});
setNextId(nextId + 1);
setNames(nextNames);
setInputText('');
input.current.focus();
}
const onEnter = (e) => {
if (e.key === 'Enter') {
onClick();
}
}
return (
<>
<input ref={input} value={inputText} onChange={onChange} onKeyPress={onEnter} />
<button onClick={onClick} >Add</button>
<ul>{namesList}</ul>
</>
);
};
export default IterationSample;
Here you use ()=> a function in onDoubleClick={() => onRemove(name.id)
but, not either in onKeyPress={onEnter} or onChange={onChange}
So when do you use a function in props?

All three of your examples use a function:
onDoubleClick={() => onRemove(name.id)}
onKeyPress={onEnter}
onChange={onChange}
The only difference is that one of them needs to specify an argument to the function (onRemove), whereas the other two do not.
If the arguments being passed by the property (in this case onKeyPress and onChange) are exactly what the function needs, then you just reference the function as-is. But if you need to customize what you're passing to the function (in this case onRemove), you can wrap it in a new function to enclose that customization.

You basically use it when you need to pass extra paremeters (except event itselft).
ex. when you want to pass id in your case you do:
onClick={(e) => onRemove(name.id)}
//or together with the event
onClick={(e) => onRemove(e, name.id)}
Otherwise when you dont need to pass a parameter inside a function, or you only need to pass event you can write it like:
onClick={onRemove}

This
onClick={onClick}
is equivalent to
onClick={(e) => onClick(e)}
For the first case, you are passing the function itself as the props.
For the second case, you are defining an inline function that calls onClick function with the parameter (e).
Generally you can just pass the function itself as the props unless you want to add additional parameters when calling your function. For example,
onClick={(e) => onClick(e, name.id)}

:)
David and Ertan answer are really good and complete.
I will just add two more things.
First, considering reactjs good practice and perfomance do not use arrow function in render.
According to eslint rules eslint rules :
A bind call or arrow function in a JSX prop will create a brand new function on every single render. This is bad for performance, as it may cause unnecessary re-renders if a brand new function is passed as a prop to a component that uses reference equality check on the prop to determine if it should update.
Secondly in the case you need to customize what you're passing to the function (in this case on Remove) you can use an alternative from lodash : partial
the official doc
Here an exemple with your code :
const namesList = names.map(name =>
<li key={name.id}
onDoubleClick={ _.partial(this.onRemove, name.id)}
>
{name.text}
</li>
);
have a good day

Just to clarify, since it seems like you may not be getting quite the beginner-friendly answer that you're looking for:
Something like "onKeyPress" or "onChange" comes with an implicit event. What does that mean? It means that whatever function you tie into that component, it will be triggered with an "event," and that will be the argument of any function that you feed in. In fact, if you look at the functions themselves, you'll notice that they accept an argument, 'e' for event. So, if it helps, you can think of some of those commands as saying
onKeyPress={()=>onEnter(keyPressEvent)}
The events will have some useful data in them, but typically the only thing that we'll care about is something like which key the user pressed, or (in the event of a change) what the new content of an input is.
For something like onDoubleClick, you're giving it some extra data. You can do some fancy footwork with javascript to identify what was double clicked and what its attributes were, but that's a pain in the butt, adds unnecessary code, and tends to obfuscate your intent in whatever it is you were coding. So it's ultimately easier to just feed the function whatever you want it to have.

Related

React functional components in Array.map are always rerendering when getting passed a function as props

I am trying to render multiple buttons in a parent component that manages all child states centrally. This means that the parent stores e.g. the click state, the disabled state for each button in his own state using useState and passes it as props to the childs. Additionally the onClick function is also defined inside of the parent component and is passed down to each child. At the moment I am doing this like the following:
const [isSelected, setIsSelected] = useState(Array(49).fill(false));
...
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
}, []);
...
(In the render function:)
return isSelected.map((isFieldSelected, key) => {
<React.Fragment key={key}>
<TheChildComponent
isSelected={isFieldSelected}
onClick={onClick}
/>
</React.Fragment/>
})
To try to prevent the child component from rerendering I am using...
... useCallback to make react see that the onClick function always stays the same
... React.Fragment to make react find a component again because otherwise a child would not have a unique id or sth similar
The child component is exported as:
export default React.memo(TheChildComponent, compareEquality) with
const compareEquality = (prev, next) => {
console.log(prev, next);
return prev.isSelected === next.isSelected;
}
Somehow the log line in compareEquality is never executed and therefore I know that compareEquality is never executed. I don't know why this is happening either.
I have checked all blogs, previous Stackoverflow questions etc. but could not yet find a way to prevent the child components from being rerendered every time that at least one component executes the onClick function and by doing that updated the isSelected state.
I would be very happy if someone could point me in the right direction or explain where my problem is coming from.
Thanks in advance!
This code will actually generate a new onClick function every render, because useCallback isn't given a deps array:
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
});
The following should only create one onClick function and re-use it throughout all renders:
const onClick = useCallback((index) => {
const newIsSelected = [...prev];
newIsSelected[i] = !newIsSelected[i];
return newIsSelected;
}, []);
Combined with vanilla React.memo, this should then prevent the children from re-rendering except when isSelected changes. (Your second argument to React.memo should have also fixed this -- I'm not sure why that didn't work.)
As a side note, you can simplify this code:
<React.Fragment key={key}>
<TheChildComponent
isSelected={isFieldSelected}
onClick={onClick}
/>
</React.Fragment/>
to the following:
<TheChildComponent key={key}
isSelected={isFieldSelected}
onClick={onClick}
/>
(assuming you indeed only need a single component in the body of the map).
Turns out the only problem was neither useCallback, useMemo or anything similar.
In the render function of the parent component I did not directly use
return isSelected.map(...)
I included that part from a seperate, very simple component like this:
const Fields = () => {
return isSelected.map((isFieldSelected, i) => (
<TheChildComponent
key={i}
isSelected={isFieldSelected}
onClick={onClick}
/>
));
};
That is where my problem was. When moving the code from the seperate component Fields into the return statement of the parent component the rerendering error vanished.
Still, thanks for the help.

Cannot read property 'length' of undefined in functional component while it works in class component [duplicate]

I'm finding these two pieces of the React Hooks docs a little confusing. Which one is the best practice for updating a state object using the state hook?
Imagine a want to make the following state update:
INITIAL_STATE = {
propA: true,
propB: true
}
stateAfter = {
propA: true,
propB: false // Changing this property
}
OPTION 1
From the Using the React Hook article, we get that this is possible:
const [count, setCount] = useState(0);
setCount(count + 1);
So I could do:
const [myState, setMyState] = useState(INITIAL_STATE);
And then:
setMyState({
...myState,
propB: false
});
OPTION 2
And from the Hooks Reference we get that:
Unlike the setState method found in class components, useState does
not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
As far as I know, both works. So, what is the difference? Which one is the best practice? Should I use pass the function (OPTION 2) to access the previous state, or should I simply access the current state with spread syntax (OPTION 1)?
Both options are valid, but just as with setState in a class component you need to be careful when updating state derived from something that already is in state.
If you e.g. update a count twice in a row, it will not work as expected if you don't use the function version of updating the state.
const { useState } = React;
function App() {
const [count, setCount] = useState(0);
function brokenIncrement() {
setCount(count + 1);
setCount(count + 1);
}
function increment() {
setCount(count => count + 1);
setCount(count => count + 1);
}
return (
<div>
<div>{count}</div>
<button onClick={brokenIncrement}>Broken increment</button>
<button onClick={increment}>Increment</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
If anyone is searching for useState() hooks update for object
Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fName}
type="text"
onChange={handleChange}
name="fName"
/>
<input
value={state.lName}
type="text"
onChange={handleChange}
name="lName"
/>
Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
The best practice is to use separate calls:
const [a, setA] = useState(true);
const [b, setB] = useState(true);
Option 1 might lead to more bugs because such code often end up inside a closure which has an outdated value of myState.
Option 2 should be used when the new state is based on the old one:
setCount(count => count + 1);
For complex state structure consider using useReducer
For complex structures that share some shape and logic you can create a custom hook:
function useField(defaultValue) {
const [value, setValue] = useState(defaultValue);
const [dirty, setDirty] = useState(false);
const [touched, setTouched] = useState(false);
function handleChange(e) {
setValue(e.target.value);
setTouched(true);
}
return {
value, setValue,
dirty, setDirty,
touched, setTouched,
handleChange
}
}
function MyComponent() {
const username = useField('some username');
const email = useField('some#mail.com');
return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
Which one is the best practice for updating a state object using the state hook?
They are both valid as other answers have pointed out.
what is the difference?
It seems like the confusion is due to "Unlike the setState method found in class components, useState does not automatically merge update objects", especially the "merge" part.
Let's compare this.setState & useState
class SetStateApp extends React.Component {
state = {
propA: true,
propB: true
};
toggle = e => {
const { name } = e.target;
this.setState(
prevState => ({
[name]: !prevState[name]
}),
() => console.log(`this.state`, this.state)
);
};
...
}
function HooksApp() {
const INITIAL_STATE = { propA: true, propB: true };
const [myState, setMyState] = React.useState(INITIAL_STATE);
const { propA, propB } = myState;
function toggle(e) {
const { name } = e.target;
setMyState({ [name]: !myState[name] });
}
...
}
Both of them toggles propA/B in toggle handler.
And they both update just one prop passed as e.target.name.
Check out the difference it makes when you update just one property in setMyState.
Following demo shows that clicking on propA throws an error(which occurs setMyState only),
You can following along
Warning: A component is changing a controlled input of type checkbox to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It's because when you click on propA checkbox, propB value is dropped and only propA value is toggled thus making propB's checked value as undefined making the checkbox uncontrolled.
And the this.setState updates only one property at a time but it merges other property thus the checkboxes stay controlled.
I dug thru the source code and the behavior is due to useState calling useReducer
Internally, useState calls useReducer, which returns whatever state a reducer returns.
https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
currentHookNameInDev = 'useState';
...
try {
return updateState(initialState);
} finally {
...
}
},
where updateState is the internal implementation for useReducer.
function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
currentHookNameInDev = 'useReducer';
updateHookTypesDev();
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher.current = prevDispatcher;
}
},
If you are familiar with Redux, you normally return a new object by spreading over previous state as you did in option 1.
setMyState({
...myState,
propB: false
});
So if you set just one property, other properties are not merged.
One or more options regarding state type can be suitable depending on your usecase
Generally you could follow the following rules to decide the sort of state that you want
First: Are the individual states related
If the individual state that you have in your application are related to one other then you can choose to group them together in an object. Else its better to keep them separate and use multiple useState so that when dealing with specific handlers you are only updating the relavant state property and are not concerned about the others
For instance, user properties such as name, email are related and you can group them together Whereas for maintaining multiple counters you can make use of multiple useState hooks
Second: Is the logic to update state complex and depends on the handler or user interaction
In the above case its better to make use of useReducer for state definition. Such kind of scenario is very common when you are trying to create for example and todo app where you want to update, create and delete elements on different interactions
Should I use pass the function (OPTION 2) to access the previous
state, or should I simply access the current state with spread syntax
(OPTION 1)?
state updates using hooks are also batched and hence whenever you want to update state based on previous one its better to use the callback pattern.
The callback pattern to update state also comes in handy when the setter doesn't receive updated value from enclosed closure due to it being defined only once. An example of such as case if the useEffect being called only on initial render when adds a listener that updates state on an event.
Both are perfectly fine for that use case. The functional argument that you pass to setState is only really useful when you want to conditionally set the state by diffing the previous state (I mean you can just do it with logic surrounding the call to setState but I think it looks cleaner in the function) or if you set state in a closure that doesn't have immediate access to the freshest version of previous state.
An example being something like an event listener that is only bound once (for whatever reason) on mount to the window. E.g.
useEffect(function() {
window.addEventListener("click", handleClick)
}, [])
function handleClick() {
setState(prevState => ({...prevState, new: true }))
}
If handleClick was only setting the state using option 1, it would look like setState({...prevState, new: true }). However, this would likely introduce a bug because prevState would only capture the state on initial render and not from any updates. The function argument passed to setState would always have access to the most recent iteration of your state.
Both options are valid but they do make a difference.
Use Option 1 (setCount(count + 1)) if
Property doesn't matter visually when it updates browser
Sacrifice refresh rate for performance
Updating input state based on event (ie event.target.value); if you use Option 2, it will set event to null due to performance reasons unless you have event.persist() - Refer to event pooling.
Use Option 2 (setCount(c => c + 1)) if
Property does matter when it updates on the browser
Sacrifice performance for better refresh rate
I noticed this issue when some Alerts with autoclose feature that should close sequentially closed in batches.
Note: I don't have stats proving the difference in performance but its based on a React conference on React 16 performance optimizations.
I find it very convenient to use useReducer hook for managing complex state, instead of useState. You initialize state and updating function like this:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => {...state, ...updates},
initialState
);
And then you're able to update your state by only passing partial updates:
updateState({ occupation: "postman" })
The solution I am going to propose is much simpler and easier to not mess up than the ones above, and has the same usage as the useState API.
Use the npm package use-merge-state (here). Add it to your dependencies, then, use it like:
const useMergeState = require("use-merge-state") // Import
const [state, setState] = useMergeState(initial_state, {merge: true}) // Declare
setState(new_state) // Just like you set a new state with 'useState'
Hope this helps everyone. :)

What gets passed where and why between various constructions (const, function, arrow functions, IIFEs) in JavaScript/React

I'm trying to understand this (radically simplified for purposes of asking this question) bit of code from Wordpress:
function MyFooContainer( {
attr1,
attr2,
attr3,
someVar
} ) {
// do some stuff
return (
<>
// some components
</>
);
}
const MyFooWrapper = withDispatch( (dispatch, ownProps, registry) => ( {
// a bunch of code doing stuff, but no returns
})
)( MyFooContainer );
const MyExportedFunction = (props) => {
const { someVar } = props;
const myTest = true;
const Component = myTest
? MyFooWrapper
: SomethingElse;
return <Component { ...props } />
};
export default MyExportedFunction;
The way I understand things (which is probably poorly, as I'm new to both JS and React), someone calls MyExportedFunction with a bunch of parameters. One of those gets explicitly pulled out into someVar, by name. All of them get passed to Component as individual arguments (because of the ellipsis expansion). Component because of the way the test is set up, is just MyFooWrapper, which takes three arguments in order, so ... the first three props had better map to dispatch, ownProps, and registry? After that, I was really confused, but I guess base on this question that
const myFunction = withDispatch((args) => { })(MyFooContainer);
is an IIFE and MyFooContaner is passed as an argument to the withDispatch? But where did MyFooContainer get its arguments?
While we're here, why is MyFooContainer explicitly defined as a function, whereas the others are assigned as const? And last but not least, where does its return go? Who is catching that?

React-Admin: How do I call two endpoints simultaneously from one click event within a <form>

So I have a form that contains the data about two tables on the backend:
table zone, and
table area
Normally, the update method in react-admin is just straight forward, but in this case not really.So I have taken the data from the zone and area tables and put it in one form.
I also altered the saveButton to tweak the form values before submitting the form according to this react-admin documentation so in this way values are in correct form before submitting.
The challenge is that...
When I click the button somehow in onClick, I would need to execute the update on 2 endpoints with 2 update calls to dataProvider. I was thinking to use redux-saga for this case, but didn't have luck yet.
Any ideas, hints, or code examples are welcome.
Two alternatives are possible here:
Call the two dataProvider.updates within a single click. Alternatively, you can fetch() directly for greater flexibility. (You might want to make use of React.useCallback and async for better performance)
export const SaveButton = ({
basePath = '',
label = 'Save',
record,
...rest
}) => {
const handleClick = () => {
if (record !== undefined) {
// first call -> Zone
async () => await dataProvider.update(...);
// Second call -> Area
async() => await dataProvider.update(...);
}
};
return <Button label={label} onClick={handleClick} {...rest} />;
};
Declare two functions to call each call dataProvider.update separately.This would give you flexibility if you intend to do the same calls once any other clickEvent is triggered within that same component
export const SaveButton = ({
basePath = '',
label = 'Save',
record,
...rest
}) => {
const handleClick = () => {
if (record !== undefined) {
// first call -> Zone
handleZoneCall();
// second call -> Area
handleAreaCall();
}
};
const handleZoneCall = async () => await dataProvider.update(...);
const handleAreaCall = async() => await dataProvider.update(...);
return <Button label={label} onClick={handleClick} {...rest} />;
};
One of the above should get you going...

Is there a React hooks feature that has the same functionality as passing a callback as the second argument to setState?

Using hooks, I would like to execute a function only after a particular call to update state. For instance, I would like to achieve the same functionality that this does (assuming I already have already instantiated this piece of state.)
setState({name: Joe}, () => console.log('hi'));
I do not want this to log 'hi' every time that name changes, I only want to log 'hi' after this particular setState call has been executed.
This
const [name, setName] = useState('');
useEffect(() => {
console.log('hi');
}, [name]);
setName('Joe');
setName('Bob'); // only log 'hi' after this one!
setName('Joe');
setName('Bob');
will not work for my purposes because I don't want to log 'hi' every time name changes. The value in the setName call does not matter. The console.log must be executed only after this particular setName call has been executed.
Update: I was overthinking this. I was asking this question because I had a piece of state called "mode" that determined some conditional rendering through a switch statement:
switch(mode) {
case foo: return <Foo />
case bar: return <Bar />
}
I was only wanting to fire some logic when mode was a certain value (aka a certain component would be rendered). I simply moved this logic down a level into the lower-level component and used
React.useEffect(() => {
someLogic();
}, []);
in order to only fire this logic on component render.
State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Just spitballing here
const useStateWithCallback = val => {
const callback = useRef();
const [state,setState] = useState(val);
useEffect(() => {
callback.current && callback.current(state);
callback.current = null;
},[callback,state]);
return [
state,
useCallback((arg,cb) => {
callback.current = cb;
return setState(arg);
},[callback,setState])) // these are safe to omit, right?
]
}
EDIT: not to be too verbose, but usage:
import { useStateWithCallback } from './useStateWithCallback';
const MyCmp = () => {
const [name,setName] = useStateWithCallback('');
...
setName('joe',state => console.log(`Hi ${state}`));
...
}

Categories

Resources