Datepicker component breaking an edit screen, using #unform and react-datepicker - javascript

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 :

Related

How to pass setState into another function and use it to target value from material-ui select and handle change, "value is undefined" error

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

Insert value of a select with axios in reactjs

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

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix 😅
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this 😇
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

Redux-React : How to pass the updated state to function onChange?

I am new to React and was trying a simple thing. I do not understand how to modify the state and pass it to the function. Please find my code below :
I am skipping the redundant code, only passing the point of focus, everything works fine except this functionality.
state = {
card: this.props.card // No probelm here , the props are correctly received in my component
};
I am trying update the state onChange and use this state value in my dispatcher to generate a new state after this event. Please find the code snippet of this functionality here :
<select
class="form-control m-1"
value={this.state.card.type}
onChange={e => {
let newType = e.target.value;
this.setState(prevState => ({
card: {
...prevState.card,
type: newType
}
}));
console.log(this.state.card) // **This gives me the old state, not updated one**
this.props.updateCard(this.state.card) // Correctly receiving the updateCard Props ,
}}
>
<option value="ABC">Option1</option>
<option value="DEF">Option2</option>
</select>
My Dispatcher :
updateCard: card=> {
dispatch({ type: "UPDATE_CARD", card: card})}
My Reducer :
case "UPDATE_CARD": {
console.log("INSIDE REDUCER");
console.log(action.card);
return {
cards: state.cards.map(card=>
card.id === action.card.id ? action.card: card
)
};
}
Please help on this. I did search a lot of stuff here but nothing was helpful.
That's because setState is not synchronous:
...
onChange ={e => {
let newType = e.target.value;
this.setState(prevState => ({
card: {
...prevState.card,
type: newType
}
}), () => {
console.log(this.state.card) // will give you the new value
// you should also do any updates to redux state here to trigger
// re-renders in the correct sequence and prevent race conditions
});
console.log(this.state.card) // **This gives me the old state, not updated one**
this.props.updateCard(this.state.card) // Correctly receiving the updateCard Props ,
}}
...

Cannot read property of undefined React Hooks

I am trying to create a search feature using react hooks but it keeps returning the error:
Cannot read Property of Undefined
on the updateSearch function whenever I type in the input field.
const [search, setSearch] = React.useState('');
const [searchResults, setSearchResults] = React.useState([]);
const [state, setState] = React.useState({
open: false,
name: '',
users:[]
});
useEffect(() => {
getAllUsers();
}, []);
const getAllUsers = () => {
fetch('/userinformation/', {
method: 'GET',
headers: {'Content-Type':'application/json'}
})
.then(function(response) {
return response.json()
}).then(function(body) {
console.log(body);
setState({...state, users: body });
})
}
const updateSearch = (event) => {
setSearch(event.target.value)
}
React.useEffect(() => {
const results = state.users.filter(user =>
user.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);
return (
<input type="text" value={search} onChange={(e) => updateSearch(e.target.value)}/>
)
Whenever I type in the search bar I get the following error:
How can i fix this?
You can either get to the value of passed event by changing
<input type="text" value={search} onChange={(event) => updateSearch(event}/>
or you can keep the input element as it is and change the update updateSearch callback to
const updateSearch = (event) => { setSearch(event) }
Secondly, you are applying includes to a single item of an array which is specific to array methods, you need to make following changes as well to make it work:
React.useEffect(() => {
const results = state.users.filter( user => user.firstName.toLowerCase() === search );
setSearchResults(results);
}, [search])
in your input you're already passing valueonChange={(e) => updateSearch(e.target.value) and in updateSearch you're trying to accessing it. Change it like this, if you want to access event in updateSearch method and get value from it.
<input type="text" value={search} onChange={(e) => updateSearch(e}/>
I would teach you a secret that has worked very well for me over the years. When javascript gives you such error as cannot read property ***whateverValue*** value of undefined it means you are trying to read the property of an object that does not exist. In this case, the object you're trying to read from is undefined, hence it cannot have any key: value pair.
Back to your question: TypeError: Cannot read property value of undefined
Using cmd+f to check for all places where value is used shows me everywhere you used value on event.target.value
Stay with me (I know this is boring, but it would help later).
You have an event handler named updateSearch.
All you need here now is to change your input tag to this:
<input type="text" value={search} onChange={updateSearch}/>
Don't worry, React would handle the rest, it automatically parses the event to eventHandler which can then be accessed on such eventHandler.
Also, I think you might want to refactor this component.
Something like import React, {useState, useEffect} from React
you won't have to call React.useEffect or React.useState in other parts of the project. just useEffect or useState.
Goodluck :+1
You have already passed the value of the input into the updateSearch method.
This is how you should fix it:
const updateSearch = (value) => {
setSearch(value);
};
And as for the second issue you have raised on your useEffect hook, you will have to call toLowerCase() on one of your properties (either firstName or lastName), depending on what you need to search for.
React.useEffect(() => {
const results = state.users.filter(user =>
user.firstName.toLowerCase().includes(search)
);
setSearchResults(results);
}, [search]);

Categories

Resources