Handle multiple input value change within an object - javascript

The requirement is handle the change of multiple input fields within the handleChange() method and the starter object that has it's own properties, so input field values should be assigned to those properties within the starter object. So far, the handleChange() method results the following. Any ideas ? =(
Constructor:
constructor() {
super();
this.state = {
starter: {
title: '',
ingredients: '',
price: 0
}
};
this.createStarter = this.createStarter.bind(this);
this.handleChange = this.handleChange.bind(this);
}
Handle change:
handleChange(evt) {
this.setState({
starter: {
[evt.target.name]: evt.target.value
}
});
}
Render:
render() {
const { t } = this.props;
const { starter: { title, ingredients, price } } = this.state;
<input
className="contact-control"
type="text"
name="title"
value={title}
onChange={this.handleChange}
placeholder={t('starter-form.title')}
/>
<input
className="contact-control"
type="text"
name="ingredients"
value={ingredients}
onChange={this.handleChange}
placeholder={t('starter-form.ingredients')}
/>
}

You can copy the existing state using object destructor:
handleChange(evt) {
this.setState({
starter: {
...this.state.starter
[evt.target.name]: evt.target.value
}
});
}

Related

I want to control value of input in parent component (Child is PureComponent)

Before question, sorry for massy code because I'm newbie at code.
I made Input component by PureComponent.
But I have no idea how to control(like change state) & submit its value in Parent component.
this is Input PureComponent I made:
index.jsx
class Input extends PureComponent {
constructor(props) {
super(props);
this.state = {
value: props.value,
name: props.value,
};
this.setRef = this.setRef.bind(this);
this.handleChange = this.handleChange.bind(this);
}
setRef(ref) {
this.ref = ref;
}
handleChange(e) {
const { name, onChange, type } = this.props;
onChange(name, e.target.value);
this.setState({ isInputError: checkError, value: e.target.value });
}
render() {
const { label, name, type} = this.props;
return (
<div className="inputBox">
<input
id={name}
value={this.state.value}
type={type}
ref={this.setRef}
onChange={this.handleChange}
className="BlueInput"
/>
<label>{label}</label>
</div>
);
}
}
Input.propTypes = {
label: PropTypes.string,
name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
type: PropTypes.oneOf(["text", "password", "number", "price", "tel"]),
onChange: PropTypes.func,
}
Input.defaultProps = {
value: "",
type: "text",
onChange: () => { },
}
export default Input;
And this is Parent component where I want to control value
Join.js
function Join() {
const [idMessage, setIdMessage] = useState("");
const [isId, setIsId] = useState(false);
const onChangeId = (name, value) => {
const currentId = value;
const idRegExp = /^[a-zA-z0-9]{4,12}$/;
if (!idRegExp.test(currentId)) {
setIdMessage("Wrong format");
setIsId(false);
} else {
setIdMessage("You can use this");
setIsId(true);
}
}
const handleSubmit = (e) => {
e.preventDefault();
let formData = new FormData();
console.log(formData);
alert("Join completed.");
}
return (
<div className="join-wrap">
<form encType="multipart/form-data" onSubmit={handleSubmit}>
<Input name="id" label="ID" onChange={onChangeId} />
<p className={isId ? 'possible' : 'impossible'}>{idMessage}</p>
<button type="submit">Join</button>
</form>
</div>
)
}
export default Join;
How can I control or submit value?
For anyone landing here with a similar question, I would advise you to use functional components. For the custom input component, you do not really need to define any state in the custom component. You can pass the state (value of input) as well as the change handler as prop to the child component. In case you need refs, use forwardRef.
Intentionally, I am not spoon-feeding here. I am just giving food for thought and some keyword to do a quick research and enhance your skills.
index.js
handleChange(e, fieldName) {
this.props.setState({...this.props.state, [fieldName]:e.target.value})
}
-----------------------
<input
id={this.props.name}
value={this.props.state[name]}
type={type} // you can also add type in state
onChange={(e)=>this.handleChange(e, name)}
className="BlueInput"
/>
join.js
const [state,setState] = useState({fName:'',lNAme:''})
{Object.keys(state).map((value)=>{
return <Input name={value} label="ID" state={state} setState = {setState}/>
})}

Input output does not update straight away it is delayed

I have created two inputs and i update the state using setState. When i type in the input and then console.log it doesn't log it straight away.
For example if i type "a n" into the input it will console log the "a" on the second key stroke...
I know that this.setState doesn't update straight away and batch updates the state, but when i have done this in the past it has worked. Why isn't my input value updating with React?
How can i get my inputs to update straight away? I want this to happen as i need to use the input by the user as a search keyword and pass each input value to a function.
code:
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
movieTitleSearch: "",
searchByYear: "",
};
}
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState({
...this.state, [event.target.name]: event.target.value,
},
() => {
if (movieTitleSearch || searchByYear > 1) {
this.props.movieSearch(movieTitleSearch, searchByYear);
}
console.log(movieTitleSearch) // does not update straight away in the console.
console.log(searchByYear) // does not update straight away in the console.
}
);
};
render() {
return (
<div>
<label>search film title</label>
<input
onChange={this.handleOnChange}
type="text"
name="movieTitleSearch"
placeholder="search by film"
value={this.state.movieTitleSearch}
/>
<label>search by year</label>
<input
onChange={this.handleOnChange}
type="text"
name="searchByYear"
placeholder="search by year"
value={this.state.searchByYear}
/>
</div>
);
}
}
UPDATED ONCHANGE FUNCTION
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState(
{
...this.state, [event.target.name]: event.target.value,
},
);
this.props.movieSearch(this.state.movieTitleSearch, this.state.searchByYear)
console.log(this.state.movieTitleSearch) // why when i type in the inputs does this not log to the console straight away?
console.log(this.state.searchByYear)
};
You are accessing the previous state when you use const movieTitleSearch.
You have to use it like this.state.movieTitleSearch, then you will get the updated state value.
handleOnChange = (event) => {
const { movieTitleSearch } = this.state;
const { searchByYear } = this.state;
this.setState({
...this.state, [event.target.name]: event.target.value,
},
() => {
if (movieTitleSearch || searchByYear > 1) {
this.props.movieSearch(this.state.movieTitleSearch, this.state.searchByYear);
}
console.log(this.state.movieTitleSearch)
console.log(this.state.searchByYear)
}
);
};
Also, I would suggest calling the movieSearch outside of the setState callback, because you already have its value with you inside event.target.value

Setting State with Objects from Firebase

I'm having trouble setting the state of a component in React. The component is called "Search" and uses react-select. The full component is here:
class Search extends React.Component {
constructor(props){
super(props);
let options = [];
for (var x in props.vals){
options.push({ value: props.vals[x], label: props.vals[x], searchId: x });
};
this.state = {
inputValue: '',
value: options
};
}
handleChange = (value: any, actionMeta: any) => {
if(actionMeta.action == "remove-value"){
this.props.onRemoveSearch({ searchId: actionMeta.removedValue.searchId })
}
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleSearch = ({ value, inputValue }) => {
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)], // Eventually like to take this out...
});
this.props.onSearch({ inputValue });
}
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.handleSearch({
value,
inputValue
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
className={"tags"}
components={components}
inputValue={inputValue}
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Add filters here..."
value={value}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;
You've probably noticed the strange thing that I'm doing in the constructor function. That's because I need to use data from my firebase database, which is in object form, but react-select expects an array of objects
with a "value" and "label" property. Here's what my data looks like:
To bridge the gap, I wrote a for-in loop which creates the array (called options) and passes that to state.value.
The problem: Because I'm using this "for in" loop, React doesn't recognize when the props have been changed. Thus, the react-select component doesn't re-render. How do I pass down these props (either modifying them inside the parent component or within the Search component) so that the Search component will re-render?
I would suggest not using the value state. What you do is simply copying props into your state. You can use props in render() method directly.
I reckon you use the value state because you need to update it based on user actions. In this case, you could lift this state up into the parent component.
class Parent extends React.Component {
constructor() {
this.state = { value: //structure should be the same as props.vals in ur code };
}
render() {
return (
<Search vals={this.state.value}/>
);
}
}
class Search extends React.Component {
constructor(props){
super(props);
this.state = {
inputValue: '',
};
}
render() {
const { inputValue } = this.state;
const { vals } = this.props;
let options = [];
for (var x in vals){
options.push({ value: vals[x], label: vals[x], searchId: x });
};
return (
<div className="search">
<div className="search__title">Search</div>
<Tooltip
content={this.props.tooltipContent}
direction="up"
arrow={true}
hoverDelay={400}
distance={12}
padding={"5px"}
>
<CreatableSelect
value={options}
/>
</Tooltip>
</div>
);
}
}
module.exports = Search;

React JS accessing an array nested in state

class CreateEventForm extends Component {
constructor(props) {
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
},
friendsInEvent: [],
},
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
this.setState({ friendsInEvent }, () => console.log(this.state))
}
render() {
const { friends, createEvent, addFriendsToEvent } = this.props
const { fields: { name, location, description, datetime } } = this.state
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}
return (
<div {...cssForm}>
<div>
<Label label="Event Name" />
<Field onChange={setter('name')} />
<Label label="Event Date/Time" />
<Field onChange={setter('datetime')} type="datetime-local" />
<Label label="Event Location" />
<Field onChange={setter('location')} />
<Label label="Event Description" />
<Field onChange={setter('description')} />
<SubHeader title="Add Friends to the Event..." />
<div>
{friends
.mapEntries(([friendId, frn]) => [
friendId,
<div key={friendId}>
<input
value={friendId}
type='checkbox'
onChange={this.handleChange}
defaultChecked={false} />
{frn.firstName} {frn.lastName}
</div>,
])
.toList()}
</div>
<Link to="/">
<Button style="blue" name="Done" onClick={() => createEvent(this.state.fields)} />
</Link>
</div>
</div>
)
}
}
export default CreateEventForm
This code above works and stores all of the correct values as expected. However I want to move friendsInEvent[ ] inside fields{ }. To look like:
super(props)
this.state = {
fields: {
name: '',
location: '',
description: '',
datetime: '',
friendsInEvent: [],
},
},
this.handleChange = this.handleChange.bind(this);
}
How can I achieve this? Everything I have tried breaks the handleChange(e) function or overwrites fields{ } with the friendsInEvent array.
I assume I have to change the values inside the handleChange(e) function after I move the array inside this.state.field?
Also, is there a way to merge my two functions setter and handleChange(e)? I kept them separate as they handle two different types (string and array).
Thanks.
From the looks of it you'll need to make the following changes to handleChange:
handleChange(e) {
const { checked, value } = e.target;
// Ensure that you're destructuring from fields
let { friendsInEvent } = { ...this.state.fields };
if (checked) {
friendsInEvent = [...friendsInEvent, value];
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
// You need to take a copy of state, and then supply a new copy
// of the the fields property which takes a copy of the old fields
// property, and overwrites the friendsInEvent with the new data
this.setState({
...this.state,
fields: {...this.state.fields, friendsInEvent }
}, () => console.log(this.state));
}
Here's a small React-free example of how this would work.
Why can't you do it in the below way.
handleChange(e) {
const { checked, value } = e.target
let { friendsInEvent } = { ...this.state }
if (checked) {
friendsInEvent = [...friendsInEvent, value]
} else {
friendsInEvent = friendsInEvent.filter(el => el !== value)
}
let object = {};
object.name = this.state.name;
object.location = this.state.location;
object.description = this.state.description;
object.datetime = this.state.datetime;
object.friendsInEvent = friendsInEvent;
this.setState({
fields: object
})
}
Never do setState inside render. You are not suppose to do setState inside render. Event handlers should be handled before render method but not inside of render. Try avoiding doing setState inside render.
const setter = propName => event => {
const nextFields = { ...this.state.fields }
nextFields[propName] = event.target.value
this.setState({ fields: nextFields })
}

className applied when using "addItem"

I am using addItem to add a value to a list from another component
I am adding it to this.state.movies. It appears, however it has the inactive/noresults className applied to it.
How do I determine which styling is applied to an item that has not appeared yet (ie using addItem)? Thanks
Full example on Codesandbox is here. Add an movie to the list and you will see it gets the stying applied: https://codesandbox.io/s/3OGK2pP9
Parent component where I add the item
<CreateNew addItem={item => this.setState({ movies: [{ name: item.value.name, genres: item.genres }].concat( movies, ), })} />
Child component that creates the item
class CreateNew extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
genres: '',
};
}
handleSubmit1 = (e, value) => {
e.preventDefault();
this.props.addItem(this.state);
};
onChange = e => {
this.setState({
value: { name: e.target.value },
genres: [{ name: 'Test', type: 1 }, { name: 'Foo', type: 10 }],
});
};
render() {
const { value, genres } = this.props;
return (
<form onSubmit={this.handleSubmit1}>
Add a new movie
<input onChange={this.onChange} value={value} type="text" />
<button type="submit">Add</button>
</form>
);
}
}
Was related to filtering on my const x instead of my state this.state.movies.
I changed it from const filteredResults = andFilter({x}, Object.keys(selectedFilters)); to `const filteredResults = andFilter({this.state.movies}, Object.keys(selectedFilters));

Categories

Resources