fire an onChange event while setting the initial value - reactjs

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>);
};

Related

Chakra UI - Checkbox Group

I am using Chakra UI with React Typescript and implementing a checkbox group
The default values are controlled by an outside state that is passed down as a prop.
The problem is that the CheckboxGroup doesn't accept the default values from outside source
The code is as follows:
import React, {FC, useCallback, useEffect, useState} from "react";
import { CheckboxGroup, Checkbox, VStack } from "#chakra-ui/react";
interface IGroupCheckbox {
values: StringOrNumber[],
labels: StringOrNumber[],
activeValues: StringOrNumber[]
onChange: (value:StringOrNumber[])=> void
}
const GroupCheckbox:FC<IGroupCheckbox> = ({
values,
labels,
activeValues,
onChange
}) => {
const [currActiveValues, setCurrActiveValues] = useState<StringOrNumber[]>();
const handleChange = useCallback((value:StringOrNumber[]) => {
if(value?.length === 0) {
alert('you must have at least one supported language');
return;
}
onChange(value);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(()=>{
if(activeValues) {
setCurrActiveValues(['en'])
}
},[activeValues])
return (
<CheckboxGroup
onChange={handleChange}
defaultValue={currActiveValues}
>
<VStack>
{values && labels && values.map((item:StringOrNumber, index:number)=>
{
return (
<Checkbox
key={item}
value={item}
>
{labels[index]}
</Checkbox>
)
}
)}
</VStack>
</CheckboxGroup>
)
}
export default GroupCheckbox
When I change the defaultValue parameter, instead of the state managed, to be defaultValue={['en']} it works fine, but any other input for this prop doesn't work.
I checked and triple checked that the values are correct.
Generally, passing a defaultValue prop "from an outside source" actually does work. I guess that in your code, only ['en'] works correctly because you explicitly use setCurrActiveValues(['en']) instead of setCurrActiveValues(activeValues).
The defaultValue prop will only be considered on the initial render of the component; changing the defaultValue prop afterwards will be ignored. Solution: make it a controlled component, by using the value prop instead of defaultValue.
Note that unless you pass a parameter to useState(), you will default the state variable to undefined on that initial render.
Side note: You also don't need a separate state variable currActiveValues. Instead, you can simply use activeValues directly.
const GroupCheckbox = ({ values, labels, activeValues, onChange }: IGroupCheckbox) => {
const handleChange = useCallback(
(value: StringOrNumber[]) => {
if (value?.length === 0) {
alert("you must have at least one supported language")
return
}
onChange(value)
},
[onChange]
)
return (
<CheckboxGroup onChange={handleChange} value={activeValues}>
<VStack>
{labels && values?.map((item: StringOrNumber, index: number) => (
<Checkbox key={item} value={item}>
{labels[index]}
</Checkbox>
))}
</VStack>
</CheckboxGroup>
)
}

How to set state using useState and do something else as well in onChange in React?

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.

State is one change behind in select menu

I have a dropdown menu that is used to select a State. It is used to display different data per state. I am console.log(), Whenever I go to select a state and the initial one is '' and when I change again, it seems to be one step behind. For example: My dropdown menu starts empty, I then select New York. It prints '', I then select Maine, It prints New York. I think I understand why it is happening but I cannot figure out how to fix it.
ProjectStatus.js
const [selectedState, setSelectedState] = useState('');
const handleChange = event => {
setSelectedState(event.target.value);
console.log(selectedState);
};
return (
<div className='project-status-nav'>
<h3>Project Status Page</h3>
<hr className='separator' />
<FormControl variant='filled' className={classes.formControl}>
<InputLabel htmlFor='selectState'>Select State</InputLabel>
<Select
id='selectState'
className='selectStyle'
value={selectedState}
onChange={handleChange}>
<MenuItem value={''}> </MenuItem>
<MenuItem value={'New York'}>New York</MenuItem>
<MenuItem value={'New Jersey'}>New Jersey</MenuItem>
<MenuItem value={'Colorado'}>Colorado</MenuItem>
<MenuItem value={'Florida'}>Florida</MenuItem>
<MenuItem value={'Maine'}>Maine</MenuItem>
</Select>
</FormControl>
</div>
);
That happens because state isn't immediatly updated. When the function is called, selectedState is declared and doesn't change untill the next call (update).
You can if you want observe selectedState changes with useEffect, or create an custom hook to do it like this:
// works like the React class setState, but the callback is called passing the new State
function useStateCallback(initialState) {
const [[state, cb], setState] = useState([initialState, null]);
useEffect(
() => {
cb && cb(state);
},
[state]
);
const setStateCallback = (value, callback) => setState([value, callback]);
return [state, setStateCallback];
}
function App() {
const [val, setVal] = useStateCallback(0);
const increment = () =>
setVal(val + 1, newVal => alert("Incremented: " + newVal));
}
setSelectedState is not synchronous, so when you log the value of selectedState just after calling setSelectedState, you get the current value, not the new one. That's why you're always one step behind
I faced the same problem and solved it like this.
const [breakwater, setBreakwater] = useState(props.breakwater);
useEffect( () => {
props.handleBreakwater(breakwater); //I updated the parent component's state in useEffect
},[breakwater]);
const handleprimaryBwType = (e) =>{
setBreakwater({...breakwater, primaryBwType : e.target.value});
//props.handleBreakwater(breakwater); when i updated here a selection was updating from behind
}

useRef in a dynamic context, where the amount of refs is not constant but based on a property

In my application I have a list of "chips" (per material-ui), and on clicking the delete button a delete action should be taken. The action needs to be given a reference to the chip not the button.
A naive (and wrong) implementation would look like:
function MemberList(props) {
const {userList} = this.props;
refs = {}
for (const usr.id of userList) {
refs[usr.id] = React.useRef();
}
return <>
<div >
{
userList.map(usr => {
return <UserThumbView
ref={refs[usr.id]}
key={usr.id}
user={usr}
handleDelete={(e) => {
onRemove(usr, refs[usr.id])
}}
/>
}) :
}
</div>
</>
}
However as said this is wrong, since react expects all hooks to always in the same order, and (hence) always be of the same amount. (above would actually work, until we add a state/any other hook below the for loop).
How would this be solved? Or is this the limit of functional components?
Refs are just a way to save a reference between renders. Just remember to check if it is defined before you use it. See the example code below.
function MemberList(props) {
const refs = React.useRef({});
return (
<div>
{props.userList.map(user => (
<UserThumbView
handleDelete={(e) => onRemove(user, refs[user.id])}
ref={el => refs.current[user.id] = el}
key={user.id}
user={user}
/>
})}
</div>
)
}

naming convention for react hooks and props?

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.

Resources