I'm developing an app in React.Js and for some reason it is not saving the value that is selected in the select.
const App = () => {
// is obtained from another api: REQUEST_API_GET
const Dates = dates.map(item => ({
value: item.Name,
label: item.Name,
}));
const [date, setDate] = React.useState({
Name: ''
});
function handleChangeDate(event) {
setDate({
Name: event.value
})
}
const addDate = (date) => {
axios.post(`${REQUEST_API_POST}`, {date});
};
return (
<>
<div>
<NoSsr>
<Select
classes={classes}
styles={selectStyles}
inputId="date"
TextFieldProps={{
label: 'Date',
InputLabelProps: {
htmlFor: 'date',
shrink: true,
},
placeholder: 'Search',
}}
options={date}
components={components}
value={date.Name}
onChange={handleChangeDate}
/>
</NoSsr>
<a onClick={() => addDate()}>add</a>
</div>
</>
)
}
export default App;
It seems to take the value when I select it, you can see in handleChangeDate if put a console.log, but in addDate it is not taking it.
How can I fix it, suggestions?
The variable date you're using in addDate is not the hook.
Just remove date parameter.
const addDate = () => { // <-- no date in input
axios.post(`${REQUEST_API_POST}`, {date});
};
Just at looking at your code it is obvious that you are not actually using the proper data in your addDate handler.
You need to pass the state value in the param or even better directly in your callback as follows:
const addDate = () => {
axios.post(`${REQUEST_API_POST}`, {date}); // here your date is your state!
};
Since you are not passing any parameters, you can improve your callback in your onClick like that too :
<a onClick={addDate}>add</a>
Another detail, it's more common to have your object properties as lowercase values hence Name to name.
When calling addDate() function you are not passing any argument, but in your parameter you have specified date as a parameter (that too without any default value). I think you are trying to access the value of date, which is a state, and state can be accessed anywhere in the function, so no need to pass it as a parameter.
enter code here
const addDate = () => {
axios.post(`${REQUEST_API_POST}`, {date});
};
Use this and it will work fine
You can also pass date (state) as an argument in you function addData, something
like this(addData(date)) and this will work too. but first approach is better
Related
I am using a material ui select component, which is filled with data from an array with values and options.
In array there is also nested object prop called "setFilter". setFilter has a value of setState, which will be later used in handleFlilter.
const [years, setYears] = useState("0");
const [hours, setHours] = useState("");
const inputs = [
{
name: "YEARS",
propValue: years,
setFilter: setYears,
placeholder: "Years",
selectOptions: optionsYears
},
{
name: "AVAILABILITY",
propValue: hours,
setFilter: setHours,
placeholder: "Availability",
selectOptions: optionsHours
}
];
const handleFilter = (e, setFilter) => {
setFilter(e.target.value);
};
<div>
{inputs.map(
({ name, propValue, placeholder, selectOptions, setFilter }) => {
return (
<div>
<CustomSelect
value={propValue}
onChange={handleFilter(setFilter)}
>
{selectOptions.map((item) => (
<StyledOption key={item.option} value={item.value}>
{item.option}
</StyledOption>
))}
</CustomSelect>
</div>
);
}
)}
The problem is, i am getting an errors called "Cannot read properties of undefined (reading 'value')"
or "e.target is undefined" in code sample.
Not sure where exactly is the problem. I am not targeting properly value, or the function is not correct ? Its not some normal html element like div, but a material select, so it should work with selected value prop.
I checked prop value, and the options are correctly visible in select list.
Here is the demo sample : https://codesandbox.io/s/unstyledselectcontrolled-material-demo-forked-wxqnrs?file=/demo.js:5335-5927
There are two issues:
You need to wrap your handler, if you want it to be fired onChange
You need to pass e (the value) to the handerFilter handler
For example:
// Only changes to your code have been shown -- the rest was removed for brevity
...
const handleFilter = (e, setFilter) => {
// Change: `e` in this case does not appear to be the event, but the value itself
setFilter(e);
};
...
{/* Change: Wrapped Handler, passes e */}
<CustomSelect
value={propValue}
// Takes `e` and then passes that and setFilter to your handler
onChange={(e) => handleFilter(e, setFilter)}
>
...
Working CodeSandbox: https://codesandbox.io/s/unstyledselectcontrolled-material-demo-forked-e49v8s
I have a several ReactSelect components, and one piece of global state which is responsible for holding all my selected values in an array.
Select
<Select
styles={inputStyles}
className="basic-single"
classNamePrefix="select"
isClearable={true}
isSearchable={false}
placeholder={'Select Your Most Convenient Time Slot'}
options={newHoursArr}
isMulti={false}
onChange={(values) => handleChange(values)}
defaultValue={clientServiceReferral.selectedTimeSlots.map((referral) => (
referral.timeSlot === timeWithDate ? (
{ ['label']: referral.value, ['value']: referral.value }
) : null
))}
/>
handleChange function
const handleChange = (value) => {
const found = clientServiceReferral.selectedTimeSlots.find(element => element.timeSlot === timeWithDate);
if (found) {
clientServiceReferral.selectedTimeSlots.splice(found, 1)
}
const newValue = {
timeSlot: timeWithDate,
value: value.value
}
setClientServiceReferral({
...clientServiceReferral,
selectedTimeSlots: [...clientServiceReferral.selectedTimeSlots, newValue]
})
}
ReactSelect has an isClearable prop. Which allows users to clear the input with a button click. This returns a value of null when values is logged in the onChange function, but is there a way to return the actual value inside the select that is getting cleared when the clear button is clicked?
There's an optional second parameter passed to the onChange event. It's of this type:
export interface ActionMetaBase<Option> {
option?: Option | undefined;
removedValue?: Option;
removedValues?: Options<Option>;
name?: string;
}
Now, I've never used this library, but it looks like removedValue or removedValues could be helpful? idk.
Anyway, I got that from their docs. Hope it works out for you:
For anyone interested, Via Joshua Wood's answer, the value of any cleared item(s) can be found as so:
onChange={(values, removedValue) => handleChange(values, removedValue)}
const handleChange = (value, removedValue) => {
if (removedValue.action === 'clear') {
console.log('removed', removedValue.removedValues[0])
}
// removedValues returns an array
So I have a fragment factory being passed into a Display component. The fragments have input elements. Inside Display I have an onChange handler that takes the value of the inputs and stores it in contentData[e.target.id]. This works, but switching which fragment is displayed erases their values and I'd rather it didn't. So I'm trying to set their value by passing in the state object to the factory. I'm doing it in this convoluted way to accomodate my testing framework. I need the fragments to be defined outside of any component and passed in to Display as props, and I need them all to share a state object.
My problem is setting the value. I can pass in the state object (contentData), but to make sure the value goes to the right key in the contentData data object I'm trying to hardcode it with the input's id. Except contentData doesn't exist where the fragments are defined, so I get an error about not being able to reference a particular key on an undefined dataObj.
I need to find a way to set the input values to contentData[e.target.id]. Thanks.
File where fragments are defined. Sadly not a component.
const fragments = (onChangeHandler, dataObj) => [
<Fragment key="1">
<input
type="text"
id="screen1_input1"
onChange={onChangeHandler}
value={dataObj['screen1_input1']} // this doesn't work
/>
one
</Fragment>,
<Fragment key="2">
<input
type="text"
id="screen2_input1"
onChange={onChangeHandler}
value={dataObj['screen2_input1']}
/>
two
</Fragment>
]
Display.js
const Display = ({ index, fragments }) => {
const [contentData, setContentData] = useState({})
const onChange = e => {
// set data
const newData = {
...contentData,
[e.target.id]: e.target.value
}
setContentData(newData)
};
return (
<Fragment>{fragments(onChange, contentData)[index]}</Fragment>
);
};
After conversing with you I decided to rework my response. The problem is mostly around the implementation others might provide in these arbitrary fragments.
You've said that you can define what props are passed in without restriction, that helps, what we need to do is take in these nodes that they pass in, and overwrite their onChange with ours, along with the value:
const RecursiveWrapper = props => {
const wrappedChildren = React.Children.map(
props.children,
child => {
if (child.props) {
return React.cloneElement(
child,
{
...child.props,
onChange: props.ids.includes(child.props.id) ? child.props.onChange ? (e) => {
child.props.onChange(e);
props.onChange(e);
} : props.onChange : child.props.onChange,
value: props.contentData[child.props.id] !== undefined ? props.contentData[child.props.id] : child.props.value,
},
child.props.children
? (
<RecursiveWrapper
ids={props.ids}
onChange={props.onChange}
contentData={props.contentData}
>
{child.props.children}
</RecursiveWrapper>
)
: undefined
)
}
return child
}
)
return (
<React.Fragment>
{wrappedChildren}
</React.Fragment>
)
}
const Display = ({ index, fragments, fragmentIDs }) => {
const [contentData, setContentData] = useState(fragmentIDs.reduce((acc, id) => ({
...acc, [id]: '' }), {}));
const onChange = e => {
setContentData({
...contentData,
[e.target.id]: e.target.value
})
};
const newChildren = fragments.map(fragment => <RecursiveWrapper onChange={onChange} ids={fragmentIDs} contentData={contentData}>{fragment}</RecursiveWrapper>);
return newChildren[index];
};
This code outlines the general idea. Here we are treating fragments like it is an array of nodes, not a function that produces them. Then we are taking fragments and mapping over it, and replacing the old nodes with nodes containing our desired props. Then we render them as planned.
This is an odd question about react-datetime: Why doesn't the callback allow a "name" to be returned?
I'm using Daytime twice for "start" and "end" times on a calendar scheduler. But I want to use a single "onChange" event to update state. The problem is: Datetime onChange only returns the time, it doesn't return a name or an ID or any way for me to identify which Datetime component is sending the onChange.
Here is the render() code:
<CardSubtitle>Start Date:
<Datetime
value = {this.state.start}
onChange={this.handleChange}
className = "dateTimeModule"
/>
</CardSubtitle>
<CardSubtitle>End Date:
<Datetime
value = {this.state.end}
onChange={this.handleChange}
className = "dateTimeModule"
/>
</CardSubtitle>
And here is the logic I want to use:
handleChange = (e) => {
this.setState({
bodyEdit: true,
[e.target.name]:e.target.value
})
};
But "e" only contains the date object. It appears that Datetime doesn't support a "name" return?
I'm confused, it seems like an obvious miss. How do I differentiate between these? Do I need to write separate functions for each?
Thinking about doing this:
handleStartChange = (e) => {
this.setState({
startDate: e
})
};
handleEndChange = (e) => {
this.setState({
endDate: e
})
};
But this seems like unnecessary code.
What you can do is pass yourself an additional parameter to your function:
<CardSubtitle>Start Date:
<Datetime
value = {this.state.start}
onChange={(e) => this.handleChange(e, "start")}
className = "dateTimeModule"
/>
</CardSubtitle>
<CardSubtitle>End Date:
<Datetime
value = {this.state.end}
onChange={(e) => this.handleChange(e, "end")}
className = "dateTimeModule"
/>
</CardSubtitle>
and reuse this parameter in your handleChange(e, name) function:
handleChange = (e, name) => {
this.setState({
bodyEdit: true,
[name]:e
})
};
Also make sure of what is returned by the library you are using as the "e", it doesn't seem to be a classical event but rather a moment object:
Callback trigger when the date changes. The callback receives the
selected moment object as only parameter, if the date in the input is
valid. If the date in the input is not valid, the callback receives
the value of the input (a string).
I created the following component to select dates in UnForm:
export default function DatePickerInput({ name, ...rest }) {
const datepickerRef = useRef(null);
const { fieldName, defaultValue = '', registerField } = useField(name);
const [date, setDate] = useState(defaultValue || null);
useEffect(() => {
registerField({
name: fieldName,
ref: datepickerRef.current,
path: 'props.selected',
});
}, [fieldName, registerField]);
return (
<Label htmlFor={fieldName}>
<UnInput>
<ReactDatePicker
ref={datepickerRef}
selected={date}
onChange={setDate}
dateFormat="dd/MM/yyyy"
placeholderText="dd/mm/aaaa"
writable="true"
{...rest}
/>
</UnInput>
</Label>
);
}
To save records the component is working normally, loading and saving the date I selected. When I am going to edit a record, when trying to load the date in the initial load, the page is broken and the following error is displayed:
Unhandled Rejection (TypeError): Cannot assign to read only property 'selected' of object '#<Object>'
If I comment out the line path: 'props.selected', in useEffect () the screen is not broken, but the date is not filled in the component. How do it work?
Issue :
formRef.current.setFieldValue('birthday',value) this will try to set value on provided path , in our case provided path is props.selected.
And props.selected is read-only property so you can't set value on props hence the error.
useEffect(() => {
registerField({
name: fieldName,
ref: datepickerRef.current,
path: 'props.selected', // <---- this is props, and it's readonly
clearValue: (ref) => {
ref.clear();
},
});
}, [fieldName, registerField]);
Solution :
You can remove the path and use getter and setter methods, named as getValue and setValue :
setValue : to set the initial value or whatever passed from setFieldValue
getValue : to get value on submit
useEffect(() => {
registerField({
name: fieldName,
ref: datepickerRef.current,
clearValue: ref => {
ref.clear();
},
setValue: (e, v) => {
setDate(new Date(v)); // <---- Setting up default value
},
getValue: () => {
return datepickerRef.current.props.selected; // to get selected value from Date picker's props
// OR
return Date.toString(); // to get selected value from state it self
}
});
}, [fieldName, registerField]);
WORKING DEMO :