Component's props names and local state variable names are collide. Is there any naming convention followed globally? See "selected" props and state
function Select({options,selected,onSelect}){
let [selected,setSelect]=useState(selected)
//... useeffect to update local state if props changes
function setSelectLocal(){
setSelect(e.target.value)
onSelect(e.target.value)
}
return (
<select onChange={onSelect} value={selected}>
{options.map((option)=><option>{option}</option>)}
</select>
)
}
Thanks
I would say const [selectedValue, setSelectedValue] = useState('default value').
However, what might be a better option is let the parent component handle the state and simply pass down the handler via props.
function ParentComponent() {
const [selectedValue, setSelectedValue] = useState('default value')
const onChange = (e) => {
setSelectedValue(e.target.value)
}
return (
<div>
// other stuff here
<ChildComponent options={stuff} onChange={onChange} selectedValue={selectedValue} />
</div>
)
}
function ChildComponent({ options, onChange, selectedValue }) {
return (
<select onChange={onChange} value={selectedValue}>
{options.map((option)=><option>{option}</option>)}
</select>
)
}
My Suggestion is to rename the prop variable to "org[prop name]"
function Select({options, selected: orgSelected, onSelect}){
let [selected,setSelect]=useState(orgSelected)
//... useeffect to update local state if props changes
function setSelectLocal(){
setSelect(e.target.value)
onSelect(e.target.value)
}
return (
<select onChange={onSelect} value={selected}>
{options.map((option)=><option>{option}</option>)}
</select>
)
}
Concise
I'd base the name on the semantics of either the value or the setter, for example:
const [opened, open] = useState(false);
Which works well if the natural language offers a good pair for state and action.
That's not always clear, because would should it not be open, open (adjective and verb?).
Fallback
It's often to see something like this in docs, which is a reasonable default
const [progress, set_progress] = useState({});
Because progress, progress (noun, verb) would lead to identical names.
Related
I have a component which includes another component (from headlessui/react) defined as follows:
export default function MyComponent(props) {
const [selectedState, setState] = useState('');
return (
<div>
<RadioGroup value={selectedState} onChange={setState}>
...
</RadioGroup>
</div>
)
}
In the onChange I would like to first call a function which does something and then calls setState. However, nomatter what I try, I can't get this to work.
I've tried:
onChange={() => {
doSomethingFirst();
return setState;
}
onChange={() => {
doSomethingFirst();
// This requires an argument and I'm not sure what the argument should be
setState();
}
// Even this fails
onChange={() => setState;}
What do I do to get this working?
When you pass onChange directly to RadioGroup it will invoke your setState with any arguments the RadioGroup supplies. Because setState only takes one argument that's thereby equal to doing onChange={arg => setState(arg)} which already shows how to accomplish what you're trying to do. Just emulate this exact behaviour and add in your function call:
onChange={arg => {
doSomethingHere()
return setState(arg)
}}
This should works.
export default function MyComponent(props) {
const [selectedState, setState] = useState('');
const updateState = (ev)=> {
doSomethingHere();
...
setState()
}
return (
<div>
<RadioGroup value={selectedState} onChange={updateState}>
...
</RadioGroup>
</div>
);
}
ev object passed to updateState function contains <RadioGroup> element. You can inspect it with console.log to see what values it holds.
If you are trying to update the state according to the RadioGroup value, you must be able to read that value inside ev object.
I am trying to build a select field which will "select" an Entry automatically when there is only one entry in the list. My Problem is, that there will no onChange event triggered, when setting the value in the list. Is there any possibility to send these event programatically?
This is my code so far
export const SelectField = function(props) {
const classes = useStyles();
const {t} = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(undefined);
if (selectedValue === undefined && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0]);
//need to fire an event in this case
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""});
}
return (
<Select
value={selectedValue}
onChange={props.onChange()}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};
There is an important difference in passing a function as a prop and calling a function.
onChange={props.onChange()}
Will call that function every render.
onChange={props.onChange}
Will pass that function to be called by the component.
For one is the mentioned onChange() to onChange, but that is not all.
It seems the issue is connected to code outside of the provided component. One indicator of an issue is the following:
....
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""}); // This line could be faulty
}
....
I don't know what kind of value menuEntries is, but it should be a state somewhere higher in your component tree. You should also pass in the setter. Probably called something like setMenuEntries. Then call that setter in instead of mutate the prop.
setMenuEntries([...menuEntries, {"name":t("select"), "value":""}])
Only when setting a state a rerender is triggered.
In general, updating props of any function is considered a bad-practise when following the principle of immutability. Eslint can help you with signifying patterns like that.
From the looks of your code it seems to be alright, there is however one thing that might cause your problem.
return (
<Select
value={selectedValue}
onChange={props.onChange} <---
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
I changed how you set the onchange function. You did it while also calling the actual function. This will make the function fire when the component renders and not on the change of your controlled value for this select.
I have solved my problem by calling he handler by my own, if i set the initial value:
export const SelectField = function(props) {
const classes = useStyles();
const { t } = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(props.value);
// on startup load test cases
React.useEffect(() => {
if (selectedValue === props.value && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0].value);
props.onChange(props.menuEntries[0].value);
}
}, [selectedValue]);
const updateValue = function(e) {
props.onChange(e.target.value);
setSelectedValue(e.target.value);
};
return (
<Select
value={selectedValue}
onChange={updateValue}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{
(props.menuEntries.length !== 1 && props.addSelectEntry) && (
<MenuItem key={t("select")} value={t("select")}>
{t("select")}
</MenuItem>
)
}
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};
in react hook how can I empty my states and show the empty inputs in same time?
My problem is, when I reset state my Input remain full.
const [state, setState] = useState([initial])
handleInputChange(event) {
setState({
event.target.value
});
}
const resetState = () => {
setState([initial])
}
export default function newState () {
return(
<input onChange={handleInputChange()} />
<button onClick={resetState}/>
)
};
What (I think) you mean to to is this:
export default function Foo({ initial }) {
const [value, setValue] = React.useState(initial)
return(
<>
<input onChange={(event) => setValue(event.target.value)} value={value} />
<button onClick={() => setValue(initial)}>Reset</button>
</>
)
};
You have to call useState inside your components.
First problem: You have initialized state to an array, reset it to an array, but update it to an object.
Always keep your data types consistent, in this case neither seem appropriate. I would suggest just using a string value.
const [state, setState] = useState(initial)
handleInputChange(event) {
setState(event.target.value);
}
const resetState = () => {
setState(initial)
}
Remember - The useState updater is not the same as the class based setState. setState merges the previous and new state values. useState replaces. So if your state is not an object the update call setState({ newState }) is not correct anymore.
Second problem: <input onChange={handleInputChange()} /> is not right. What this code does is assign onChange the value returned from calling handleInputChange. What you actually want is to assign onChange the function itself.
// no ()
<input onChange={handleInputChange} />
Third Problem: Your state hook doesn't appear to be inside the functional component and it doesn't look like its set up for use as a custom hook (I could be wrong). Move them into the component function.
I pass a component (C) as props to a Child component (B) inside a Parent component (A). State of A is also passed to C and mapped to C's state. But when I update A's state and B's state accordingly, state of C does not update.
My code looks like this: (import statements are omitted)
const Parent = (props) => {
.............(other state)
const [info, setInfo] = React.useState(props.info);
const handleDataChanged = (d) => { setInfo(d); }
return (
<div>
........(other stuffs)
<MyModal
..........(other props)
body={ <MyComp data={ info } updateData={ handleDataChanged } /> }
/>
</div>
);
}
const MyModal = (props) => {
..........(other state)
const [content, setContent] = React.useState(props.body);
React.useEffect(() => { setContent(props.body); }, [props]);
return (
<Modal ...>
<div>{ content }</div>
</Modal>
);
}
const MyComp = (props) => {
const [data, setData] = React.useState(props.data);
React.useEffect(() => { setData(props.data); }, [props]);
return (
data && <TextField value={ data.name }
onChange={ e => {
let d = data;
d.name = e.target.value;
props.updateData(d); }} />
);
}
When I type something in the TextField, I see Parent's info changed. The useEffect of MyModal is not fired. And data in MyComp is not updated.
Update: After more checking the above code and the solution below, the problem is still, but I see that data in MyComp does get changes from Parent, but the TextField does not reflect it.
Someone please show me how can I update data from MyComp and reflect it to Parent. Many thanks!
Practically, it looks like you are trying to recreate the children api https://reactjs.org/docs/react-api.html#reactchildren.
Much easier if you use props.children to compose your components instead of passing props up and down.
const MyModal = (props) => {
...(other state)
return (
<Modal>
<div>{ props.children }</div>
</Modal>
);
}
Then you can handle functionality directly in the parent without having to map props to state (which is strongly discouraged)...
const Parent = (props) => {
...(other state)
const [info, setInfo] = React.useState(props.info);
const handleDataChanged = d => setInfo(d);
return (
<div>
...(other stuffs)
<MyModal {...props}>
<MyComp data={ info } updateData={ handleDataChanged } />
</MyModal>
</div>
);
}
The upside of this approach is that there is much less overhead. rather than passing State A to C and mapping to C's state, you can just do everything from State A (the parent component). No mapping needed, you have one source of truth for state and its easier to think about and build on.
Alternatively, if you want to stick to your current approach then just remove React.useEffect(() => { setContent(props.body); }, [props]); in MyModal and map props directly like so
<Modal>
<div>{ props.body }</div>
</Modal>
The real problem with my code is that: React Hook does not have an idea whether a specific property or element in a state object has changed or not. It only knows if the whole object has been changed.
For example: if you have an array of 3 elements or a Json object in your state. If one element in the array changes, or one property in the Json object changes, React Hook will identiy them unchanged.
Therefore to actually broadcast the change, you must deep clone your object to a copy, then set that copy back to your state. To do this, I use lodash to make a deep clone.
Ref: https://dev.to/karthick3018/common-mistake-done-while-using-react-hooks-1foj
So the code should be:
In MyComp:
onChange={e => { let d = _.cloneDeep(data); d.name = e.target.value; props.handleChange(d) }}
In Parent:
const handleChange = (data) => {
let d = _.cloneDeep(data);
setInfo(d);
}
Then pass the handleChange as delegate to MyComp as normal.
I'm trying to understand if passing the setter from useState is an issue or not.
In this example, my child component receives both the state and the setter to change it.
export const Search = () => {
const [keywords, setKeywords] = useState('');
return (
<Fragment>
<KeywordFilter
keywords={keywords}
setKeywords={setKeywords}
/>
</Fragment>
);
};
then on the child I have something like:
export const KeywordFilter: ({ keywords, setKeywords }) => {
const handleSearch = (newKeywords) => {
setKeywords(newKeywords)
};
return (
<div>
<span>{keywords}</span>
<input value={keywords} onChange={handleSearch} />
</div>
);
};
My question is, should I have a callback function on the parent to setKeywords or is it ok to pass setKeywords and call it from the child?
There's no need to create an addition function just to forward values to setKeywords, unless you want to do something with those values before hand. For example, maybe you're paranoid that the child components might send you bad data, you could do:
const [keywords, setKeywords] = useState('');
const gatedSetKeywords = useCallback((value) => {
if (typeof value !== 'string') {
console.error('Alex, you wrote another bug!');
return;
}
setKeywords(value);
}, []);
// ...
<KeywordFilter
keywords={keywords}
setKeywords={gatedSetKeywords}
/>
But most of the time you won't need to do anything like that, so passing setKeywords itself is fine.
why not?
A setter of state is just a function value from prop's view. And the call time can be anytime as long as the relative component is live.