I am developing a simple crud form with formik in react for Learning purposes, this
Now in this form I am facing two issues
Ist Issue
As Quantity field should only input numbers so I added a onChange listener and updated state if only number are inputted, it works fine, but problem is it reset's the whole form
<Field
className="form-control"
type="text"
name="quantity"
onChange={this.onChangeCheckNumber}
>
</Field>
onChangeCheckNumber = (event) => {
const re = /^[0-9\b]+$/;
// if value is not blank, then test the regex
if (event.target.value === '' || re.test(event.target.value)) {
this.setState({ quantity: event.target.value })
}
}
I think this piece of code is culprit
this.setState({ quantity: event.target.value })
As I am setting state, so I think my whole form gets re rendered again,
How to achieve this functionality perfectly ?
2nd Issue
Warehouse selection depends upon city selection, so I added a onChange on city and called warehouse for selected cityId and I am receiving data, but city drop down gets blocked.
My state properties
state = {
quantity: 0,
price: "0",
comments: '',
itemType: {
key: 0,
value: 'Select'
},
cities: {
key: 0,
value: 'Select'
},
warehouses: {
key: 0,
value: 'Select'
},
sellers: {
key: 0,
value: 'Select'
},
warehouseOptions: null
}
In componentDidMount, I am initializing the dropdown values
componentDidMount() {
this.itemTypeObject();
this.cityObject();
this.sellerObj();
}
Fetching data with axios from server and updating state
cityObject = () => {
axios.get('/cities')
.then(response => {
this.setState({
cities: response.data
});
}).catch(error => {
console.log(error);
});
}
Inside my Formik form tag my city and warehouse dropdowns
<div>
<label>City</label>
<div>
<Field
as="select"
onChange={this.onCityDropdownSelected}
name="city"
>
{this.createCity()}
</Field>
</div>
</div>
<div>
<label>Warehouse</label>
<div>
<Field
className="form-control"
as="select"
name="warehouse"
>
{this.state.warehouseOptions}
</Field>
</div>
</div>
And my onCityDropdownSelected
onCityDropdownSelected = (event) => {
if (event.target.value !== "0") {
this.warehouseObj(event.target.value);
} else {
this.setState({
warehouseOptions: null
})
}
}
warehouseObj = (cityId) => {
axios.get(`/warehouses/cityId/${cityId}`)
.then(response => {
this.setState({
warehouses: response.data
});
this.createWarehouse();
}).catch(error => {
console.log(error);
});
}
After this I get updated values in warehouse drop down, but city drop down opens , but does not change on selection.
In your 1st issue you are changing the whole state by doing this.setState({ quantity: event.target.value }) as it will change your state and clear out all other state values and final state will only contain quantity resulting reseting form state.
To solve that issue you have to de structure your old state and than just change the quantity like this:
this.setState{...state, quantity: event.target.value}
as for the onCityDropdownSelected same issue is occurring. Please do tell me if this answer helps solve your problem.
Related
I'm working on my first ever web-app and bootstrapped this app, I'm having trouble changing the URL of an API get request. I'm using react-select to create a dropdown menu, and I want the dropdown selection to alter search criteria, for example:
I have a baseurl.com/
and want to create a variable based off the dropdown selection, to append to the baseurl.
My two options from the dropdown are 'Name' and 'Birthday',
and if you select 'Name', the URL would be
baseurl.com/Patient?name= + inputvalue.
and if you select 'birthday', the URL will be
baseurl.com/Patient?birthdate=eq + inputvalue
I want to keep the baseURL as is because I will have to add more options to the select eventually. I've already got the inputvalue working in my app so I don't need to make changes to it I believe.
Here is some of my code so far, which gives me a "Cannot read property 'value' of undefined" error" when I make a selection. Also I haven't yet made the component to store the state as a variable, but I'll cross the bridge when it comes to it 😅 Any insight is appreciated, thanks :
const choice = [
{value : "Name", label: "Name" },
{value : "bDay", label: "Birthday (YYYY-MM-DD)"}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: 'Search',
queryResult: {},
criteria: '',
showSVG: false
};
this.handleChange = this.handleChange.bind(this);
this.getInputValue = this.getInputValue.bind(this);
this.baseURL = this.baseURL.bind(this);
}
getInputValue(e) {
this.setState({ inputValue: e.target.value });
}
handleChange(e) {
this.setState({criteria: e.target.value});
console.log(e.target.value);
}
baseURL(e) {
const url = 'https://baseURL.com/';
[BLOCK FOR FETCH REQUEST]
render() {
return (
<div className="input-wrapper">
{/* ON CHANGE SELECT */}
<Select options={choice} value={this.state.criteria} onChange={this.handleChange} />
<input
type="text"
value= {this.state.inputValue}
onFocus = {() => this.setState({ inputValue: '' })}
onChange={this.getInputValue}
onKeyDown={this.baseURL} />
<img className={this.state.showSVG ? "isVisable" : ""} src="assets/icons/grid.svg" />
{ Object.keys(this.state.queryResult).length !== 0 ? <PatientInfoBlock data={this.state.queryResult} /> : null }
{ !this.state.queryResult ? <h3>Sorry no results match ''{this.state.inputValue}''</h3> : null }
</div>
);
}
'''
handleChange should be:
handleChange(selectedOption) {
this.setState({criteria: selectedOption});
}
selectedOption type is:
{
value: string,
label: string
}
Figured it out, thanks to armin yahya for pointing me towards the right direction, as I was not aware of the selectedOption type. Here's what I ended up writing that has my handleChange function working properly and updating the URL for the API call:
handleChange(selectedOption) {
if(selectedOption.value == 'Name') {
this.setState({criteria: 'Patient?name='});
}
if(selectedOption.value == 'bDay') {
this.setState({criteria: 'Patient?birthdate=eq'});
}
I'm new to react and trying to learn on my own. I started using react-select to create a dropdown on a form and now I'm trying to pass the value of the option selected. My state looks like this.
this.state = {
part_id: "",
failure: ""
};
Then in my render
const {
part_id,
failure
} = this.state;
My form looks has 2 fields
<FormGroup>
<Label for="failure">Failure</Label>
<Input
type="text"
name="failure"
placeholder="Failure"
value={failure}
onChange={this.changeHandler}
required
/>
</FormGroup>
<FormGroup>
<Label for="part_id">Part</Label>
<Select
name="part_id"
value={part_id}
onChange={this.changeHandler}
options={option}
/>
</FormGroup>
the changeHandler looks like this
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
The change handler works fine for the input but the Select throws error saying cannot read property name. I went through the API docs and came up with something like this for the Select onChange
onChange={part_id => this.setState({ part_id })}
which sets the part_id as a label, value pair. Is there a way to get just the value? and also how would I implement the same with multiselect?
The return of react-select onChange event and the value props both have the type as below
event / value:
null | {value: string, label: string} | Array<{value: string, label: string}>
So what the error means is that you can't find an attribute of null (not selected), or any attributes naming as name (you need value or label)
For multiple selections, it returns the sub-list of options.
You can find the related info in their document
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
Update
For your situation (single selection)
option having type as above
const option = [
{value: '1', label: 'name1'},
{value: '2', label: 'name2'}
]
state save selected value as id
changeHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
pick selected option item via saved id
<Select
name="part_id"
value={option.find(item => item.value === part_id)}
onChange={this.changeHandler}
options={option}
/>
For multiple selections
state save as id array
changeHandler = e => {
this.setState({ part_id: e ? e.map(x => x.value) : [] });
};
pick via filter
<Select
isMulti // Add this props with value true
name="part_id"
value={option.filter(item => part_id.includes(item.value))}
onChange={this.changeHandler}
options={option}
/>
onChange function is a bit different in react-select
It passes array of selected values, you may get first one like
onChange={([selected]) => {
// React Select return object instead of value for selection
// return { value: selected };
setValue(selected)
}}
I have tried the above solutions but some of these solutions does update the state but it doesn't gets rendered on the Select value instantly.
Herewith a demo example:
this.state = {
part_id: null,
};
handleUpdate = (part_id) => {
this.setState({ part_id: part_id.value }, () =>
console.log(`Option selected:`, this.state.part_id)
);
};
const priceOptions = [
{ value: '999', label: 'Item One' },
{ value: '32.5', label: 'Item Two' },
{ value: '478', label: 'Item Three' }
]
<Select
onChange={this.handleUpdate}
value={priceOptions.find(item => item.value === part_id)}
options={priceOptions}
placeholder={<div>Select option</div>}
/>
This is a hypothetical question right now, because I am not sure if my approach is even possible. I have a bunch of input checkbox fields in a form. I am using datoCms. I am thinking that I could use the repeater module to create as many or remove checkbox fields, and give them the same label and name as what I write in a text field in dato as a repeater. Now Dato outputs a graphQL api, that I can query and it would output the repeater fields as an array that I can map over after I query it.
So I will show you my code, and what I think will work, but I just need to be pointed in the right direction, and I can update this question with my attempt, but at this point I don't even know really where to start.
The part that is confusing to me is the React.useState how would I dynamically add values to it from a map method.
Ok here is my state code
const [formState, setFormState] = React.useState({
name: "",
package: `${data.datoCmsPricing.title}`,
email: "",
subject: "",
weightLoss:"",
message: "",
})
const onChange = (e) => {
if (e.target.type === 'checkbox' && !e.target.checked) {
setFormState({...formState, [e.target.name]: ''});
} else {
setFormState({...formState, [e.target.name]: e.target.value });
}
}
and here is my form
<form onSubmit={submitForm}>
<h3>Reasons for wanting to train</h3>
<label>
Weight Loss
<input
type="checkbox"
name="weightLoss"
checked={formState.weightLoss}
onChange={onChange}
/>
</label>
<button type="submit">Submit</button>
</form>
Now this is what I would propose I do with the form to get as many checkboxes, this is basically sudo code at this point because I think it would break at the checked part
{data.datoCmsPricing.details.map(detailEntry => {
return (
<label key={detailEntry.id}>
{detailEntry.reason}
<input
type="checkbox"
name={detailEntry.reason}
checked={formState.{detailEntry.reason}}
onChange={onChange}
/>
</label>
)
})}
after this I don't know what I would do with state?
Thank you ahead of time. Link to Repo https://github.com/wispyco/jlfit
useState perfectly works for dynamic data. And your code is almost correct.
You should add dataEntries property to useState object
const [formState, setFormState] = React.useState({
name: "",
package: `package`,
email: "",
subject: "",
weightLoss:"",
message: "",
dataEntries: {
'1': { reason: 'reason1', checked: false },
'2': { reason: 'reason2', checked: false },
'3': { reason: 'reason3', checked: false },
'4': { reason: 'reason4', checked: false }, },
})
I've prefilled dataEtries with demo data. In real app this data will be fetched from backend.
Modify onChange to correctly handle dataEntries object
const onChange = e => {
let value = undefined;
if (e.target.type === "checkbox") value = e.target.checked;
else value = e.target.value;
setFormState({
...formState,
dataEntries: {
...formState.dataEntries,
[e.target.id]: {
...formState.dataEntries[e.target.id],
[e.target.name]: value
}
}
});
};
And finally set correct name and id properties on form controls, so onChange can properly update data in state.
export const Checkbox = ({ onChange, detailEntry }) => (
<form>
<label key={detailEntry.id}>
{detailEntry.reason}
<input
type="checkbox"
name="checked"
id={detailEntry.id}
checked={detailEntry.checked}
onChange={onChange}
/>
<input
id={detailEntry.id}
name="target weight"
value={detailEntry["target weight"]}
onChange={onChange}
/>
</label>
</form>
);
I've added additional field "target weight" to showcase how any additional control can be used.
Complete demo is here
Updating state works if the state i am trying to update is outside the users array. But since i am having multiple users i need the state to be inside the objects and update anyway
I keep getting the error TypeError: Cannot read property 'name' of undefined
I've thought of setting state inside of a loop but i was told thats a bad idea.
So [e.target.name]: e.target.value was the only code i could find for dropdowns.
I tried passing id for each of the users but didnt know how to change state using that or what condition to put.
import React, { Component } from 'react'
export default class App extends Component {
state = {
users: [
{
id: uuid(),
firstName: 'John',
lastName: 'Doe',
favColor: 'None'
},
{
id: uuid(),
firstName: 'Jane',
lastName: 'Doe',
favColor: 'None'
}
]
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<div>
{this.state.users.map((user) => {
return <div key={user.id}>
<h1>{user.firstName}</h1>
<h1>{user.lastName}</h1>
<form>
<select
name="favColor"
value={user.favColor}
onChange={() => this.handleChange(user.id)}
>
<option value="None" disabled>None</option>
<option value="Blue">Blue</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</form>
<h1>Fav Color: {user.favColor}</h1>
<hr />
</div>
})}
</div>
)
}
}
I expect the dropdowns to change state separately for each of the users
Your handleChange method is not accepting the correct arguments.
If you wish to update one user item in array you will need to create a new updated copy of the array and save back into state
handleChange = (e,id) => {
const updatedUser = {...this.state.users.find(x=>x.id ===id), favColor: e.target.value}
this.setState({
users: [...this.state.users.filter(x==>x.id!==id),updatedUser]
})
}
...
onChange={(e) => this.handleChange(e,user.id)}
To simplify mutations of state I can recommend taking a look at Immer
And as #JosephD rightly pointed out this won't mantain order so you will need to do something like this.state.users.map(u => u.id === id ? { ...u, favColor: e.target.value } : u)
Here is a codesandbox based on your code:
https://codesandbox.io/s/loving-cohen-do56l?fontsize=14
<select
name="favColor"
value={this.state.favColor}
onChange={(e) => this.handleChange(e)}> // first Change
handleChange = (e) => {
this.setState({
favColor: e.target.value
})
} // Second Change
This will work for you
You are updating the state the wrong way;
Your state:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
Your update / intention:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
favColor: color // intention, because you don’t pass event
To update the right way, you need to:
pass the event and currentId, to handleChange, of the selected dropdown. Otherwise you cannot know the current user. Also, in your example you don’t pass the event, so you cannot retrieve the information of the dropdown. Causing the name of undefined error.
check when id of user matches with id of dropdown and change the value.
This example should work for you.
https://codesandbox.io/s/small-sea-mw08n
The code below is only working when I remove the componentWillMount that uses localStorage. With usage localStorage it gives a mistake
this.state.interests.map is not a function
I tried to move usage of localStorage out of component but it won't help. I suppose that using local storage somehow changes this.state.interests that they stop being an array.
let interests = ["Музыка", "Компьютеры", "Радио"]
let ListOfInterest = React.createClass({
getInitialState: function() {
return {value: '', interests: interests};
},
componentWillMount() {
let local = localStorage.getItem('interests')
if (local) {
this.setState({interests: local});
} else {
localStorage.setItem('interests', this.state.interests)}
},
deleteInterest(key) {
delete interests[key]
this.setState(this.state) // without this line the page will not re-render
},
addInterest() {
interests.unshift(this.state.value)
this.setState({value: ''})
},
handleChange(event) {
this.setState({value: event.target.value})
},
render() {
return <div className="interests">
<b>Интересы</b>
<br/>
{this.state.interests.map((int, index) => {
return <button onClick={() => {
this.deleteInterest(index)
}} key={index} className="btn-interest">{int}</button>
})}
<input type='text' placeholder="Add" value={this.state.value} onChange={(e) => this.handleChange(e)}/>
<button onClick={() => {
this.addInterest()
}} className="add">Add interest</button>
</div>
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
You have several issues in your example
in localStorage.setItem second argument have to be a String, you can not store Array(when you do it, in storage will be string separated by coma because called method toString - [1, 2, 3].toString() ), you need stringify array before set to Storage
keyValue A DOMString containing the value you want to give the
key you are creating/updating.
localStorage.setItem(
'interests', JSON.stringify(this.state.interests)
)
and parse when get value
let local = JSON.parse(localStorage.getItem('interests'));
this.setState(this.state) this is not good way to update state, you need update state like so
deleteInterest(key) {
this.setState({
interests: this.state.interests.filter((el, i) => i !== key)
})
},
addInterest() {
this.setState({
value: '',
interests: this.state.interests.concat(this.state.value)
});
},
Example