Generic function in react with multiple state variables - javascript

How to write a generic function with various state variable change based on the dropdown.
for ex:
I have dropdown 1 and dropdown 2. If I change the
dropdown 1, I need to change the few state variables like a, b, c
dropdown 2, I need to change the few state variables like x, y, z
I can do this with 2 functions. But how to write a a generic function for this?
handleChange: function(e) {
//api call to xyz.com/getcompany/<company id> to get the job list here
this.setState({
selectedCompany: e.target.value,
companychange: "company changed. New value: " + e.target.value
})
},
handleChange2: function (e) {
// api call to xyz.com/jobstatus/<job id> to get the job status\(like how many position available for this job\) here
this.setState({
jobchange:"job changed. New value " + e.target.value
})
}
Codepen: https://codepen.io/asdhanapal/pen/WmwJPj?editors=0011

You could use a curried function to simplify the code a bit:
changeToState(fn) {
return e => this.setState(fn(e));
}
render() {
//...
<input onChange={changeToState(e => ({ selectedCompany: e.target.value, /*...*/ }))} />
<input onChange={changeToState(e => ({ jobChange: e.target.value, /*...*/ }))} />
}
If that is still to much boilerplate, you could extract the handleChange event into a functional component:
const StateChanger = ({ action, parent, ...props }) => (
<input onChange={e => parent.setState(action(e))} {...props} />
);
// inside a component's render:
<StateChanger parent={this} action={e => ({ selectedCompany: e.target.value })} style={{ color: red }} />
but as I already mentioned in the comments, that might remove repeated code a bit, but it doesn't improve readability / maintainability whatsoever.

You can use below snippet:
handleChange = event => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
};
Reference to react docs section:
https://reactjs.org/docs/forms.html#handling-multiple-inputs

You can use following:
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]:value
})
}
// in input
<input name="username" onChange={this.handleChange} />

Try this:
handleChange: function(source, e) {
switch(source) {
case 'company':
//Need to do api call to get the job list here
this.setState({
selectedCompany: e.target.value,
companychange: "company changed. New value: " + e.target.value
})
break;
case 'job':
// Need to do api call to get the job status\(like how many position available for this job\) here
this.setState({
jobchange:"job changed. New value " + e.target.value
})
break;
};
},
<select value={this.state.selectedCompany} onChange={this.handleChange.bind(this, 'company')}>
<select value={this.state.selectedCompany} onChange={this.handleChange.bind(this, 'job')}>

As I read your description of the requirements, there are no functional dependencies between dropdown 1 and dropdown 2, so I'd split this up in two separate components.

Related

how to setState update property of object instance in child list

I have an object quiz, it has a list of questions, questions have properties.
I want to update a property called question in object question (i know poor naming convention), inside questions without creating a separate instance of the selected question.
This is what I have so far (but it doesn't like the square bracket of selecting the particular question being edited):
onChange={(e) => setQuiz({ ...quiz, questions[quiz.questions.indexOf(selectedQuestion)].question: e.target.value })}
error:
Unexpected token, expected ","
img of syntax:
edit:
Here is a live demo example: https://codesandbox.io/s/recursing-dan-bv54nz?file=/src/App.js
I provide here a full working example and separate answer below incase this repo dies.
Full working code:
const [quiz, setQuiz] = useState({
title: "",
number: 6,
questions: [
{ question: "item1", number: 3 },
{ question: "item2", number: 3 }
]
});
const [selectedQuestion, setSelectedQuestion] = useState(0);
useEffect(() => {
setSelectedQuestion(0);
}, []);
useEffect(() => {
console.log("quiz", quiz);
console.log("quiz question", quiz.questions[0]);
}, [quiz]);
useEffect(() => {
console.log("selectedQuestion", selectedQuestion);
}, [selectedQuestion]);
return (
<div className="App">
<input
type="text"
value={quiz.questions[selectedQuestion].question}
onChange={(e) =>
setQuiz({
...quiz,
questions: quiz.questions.map((child, index) =>
index === selectedQuestion
? { ...child, question: e.target.value }
: child
)
})
}
/>
</div>
);
}
Super compact one-liner:
onChange={e => setQuiz({ ...quiz, questions: quiz.questions.map(q => q === selectedQuestion ? { ...q, question: e.target.value } : q) })}
This piece of code is really way too compact and hard to read.
So I personally would extract it to a named function instead of inline it. One more thing, I always prefer the setState(prevState => newState) updater pattern instead of setState(newState) directly. It avoids stale closure problem.
function MyComponent(props) {
const handleChange = (e) => {
setQuiz((quiz) => {
const questions = quiz.questions.map((q) => {
if (q !== selectedQuestion) return q;
return { ...q, question: e.target.value };
});
return { ...quiz, questions };
})
};
/* ... */
return <input onChange={handleChange} />
}
Can you check whether this one work or not? (I just tried to prevent from above errors)
onChange={(e) => {
const newQuestions = quiz.questions;
newQuestions[quiz.questions.indexOf(selectedQuestion)].question = e.target.value;
setQuiz({ ...quiz, questions: newQuestions });
}}
following #hackape & #Drew Reese suggestions I came up with the following:
The only real difference being also passing an index into the questions map so that it can correctly select the right element
onChange={(e) =>
setQuiz({
...quiz,
questions: quiz.questions.map((child, index) =>
index === selectedQuestion
? { ...child, question: e.target.value }
: child
)
})
}

Gatsby only passing on checkbox value to Netlify forms

I have an array of checkboxes and I want to pass which checboxes have been selected to Netlify forms.
Right now only one of the values is passed, instead I want them all to be passed and formatted nicely (a comma in between).
My checkbox looks like this, it loops through data fetched from an array from a headless CMS and display all the checkboxes as it should
<fieldset>
<legend>Inquery type</legend>
{formData.radioButtons.map((node, index) => (
<>
<p>
<label key={index}>
<input
type="checkbox"
id={("inqueryType", index)}
name="inqueryType"
ref={register()}
value={node}
onChange={handleChange}
key={(node, "checbox")}
/>
{node}
</label>
</p>
</>
))}
</fieldset>
My handleChange function looks like this
const handleChange = e => {
if (e.target.type === "checkbox" && !e.target.checked) {
setState(prev => ({ ...prev, [e.target.name]: e.target.value }))
} else {
setState({ ...state, [e.target.name]: e.target.value })
}
}
I suspect that my error is in my handleChange method, but I have been unable to figure out why it only stores one value instead of all of them. Any ideas?
My onSubmit looks like this
const onSubmit = (data, e) => {
e.preventDefault()
fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({
"form-name": "contactGudcForm",
...state,
}),
})
.then(response => {
setFeedbackMsg(`Thanks for reaching out. I'll get back to you soon.`)
reset()
console.log(response)
})
.catch(error => {
setFeedbackMsg(
"Oops, something went wrong. The form could not be submitted."
)
console.log(error)
})
}
and my encude function, shamlessly stolen from StackOverflow looks like this
function encode(data) {
return Object.keys(data)
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
.join("&")
}
This is a Gatsby project deploying to Netlify using standard Netlify forms
The issue is that your checkboxes share the same name attribute (inqueryType in your example), therefore they will overwrite eachother here:
const handleChange = e => {
// ...
setState(prev => ({ ...prev, [e.target.name]: e.target.value }))
// ...
}
What happens is that you're adding the form data to state using the inputs' name as the key. Since your checkboxes don't have a unique name, it kind of does this:
const state = {}
// push the value of the first checkbox
state["inqueryType"] = "value 1"
// push the value of the second checkbox
state["inqueryType"] = "value 2"
// "value 1" is gone :(
Make sure that your keys are unique, and you should be fine!
Edit: as mentioned in the comments, you can format the data in any shape you like, as long as you don't overwrite it. If you want a stringified array of values for your group of checkboxes, you could do something like this:
const handleChange = e => {
if (e.target.name === "inqueryType") {
const inqueryType = [...state.inqueryType, e.target.value]
setState({ ...state, inqueryType: JSON.stringify(inqueryType) })
}
else {
setState({ ...state, [e.target.name]: e.target.value })
}
}
Of course there might be better solutions for your particular use case - this part is up to you :)

Javascript object elements is undefined

Trying to log the values of name, day and dob elements stored in dataEdited as object.Two of the elements display Undefined with just one displaying the correct value.
Here is the code
/* two-way state binding */
dataChange(event){
const target = event.target;
const value = target.value; //gets value of the textbox
const name = target.name;
this.setState({ dataEdited: {[name]: value} });
}
handleUpdate(event){
event.preventDefault();
const {name,day,dob} = this.state.dataEdited;
console.log(name, day, dob);
/* this.setState({ toggle: false }) */
}
State
this.state = {
name: '',
day: '',
dob: '',
items : [],
currentItem: {},
dataEdited: {},
toggle: false,
loading: false
}
Render
<form onSubmit={this.handleUpdate}>
<input
className=""
name="name"
onChange={this.dataChange}
defaultValue={this.state.currentItem.name}
placeholder= "Celebrant's Name"
ref={name => this.name = name}
required />
<input
className=""
type="number"
name="day"
min="1"
max="31"
ref={day => this.day = day}
onChange={this.dataChange}
defaultValue={this.state.currentItem.day}
placeholder= "day" />
<input
className=""
name="dob"
type="month"
onChange={this.dataChange}
defaultValue={this.state.currentItem.dob} />
<button type="submit">update</button>
<button onClick={this.handleEditCancel}>cancel</button>
</form>
This is the result on the console
undefined undefined "2020-08"
I don't understand how this is possible, can I get an explanation. Also, how can I fix this?
When you execute this.setState({ dataEdited: {[name]: value} }); you overwrite the other values in the object assigned to dataEdited.
You should change that to this.setState({ dataEdited: { ...this.state.dataEdited, [name]: value} }); to preserve the previous values inside this.state.dataEdited
UPDATE (Thanks #JMadelaine): You should use this.setState(prev => ({ dataEdited: { ...prev.dataEdited, [name]: value}})); to ensure that no concurrent state changes affects the setState()
More info: https://stackoverflow.com/a/50837784/10201813
Problem
you are overwriting the same dataEdited variable over and over when you call this.setState({ dataEdited: {[name]: value} });
Thus only the last changed data will be present inside dataEdited
Solution
Seperately save the data or change setState function appropriately
You are overwriting the state variable dataEdited everytime you call
this.setState({ dataEdited: {[name]: value} });
so you want to change spread the object dataEdited and the state After that add or change the name,dob or day
this.setState({
...this.state,
dataEdited: { ...this.state.dataEdited, [name]: value }
});
CodeSandbox here

React js variable in render stays undefined

I have two functions as follows :
handleChangeInputStockName = async event => {
const stockName = event.target.value
this.setState({ stockName })
}
handleChangeInputTotalQuantity = async event => {
const totalQuantity = event.target.value
this.setState({ totalQuantity })
}
I have two components that control the above two functions
<InputText
type="text"
value={ totalQuantity }
onChange={this.handleChangeInputTotalQuantity}
/>
and
<InputText
type="text"
value={ stockName }
onChange={this.handleChangeInputStockName}
/>
In my render function both stockName and totalQuantity are always undefined, even though I can see changes being made to them in the InputText via the above two functions. I am able to see a state change however it goes back to undefined immediately. The render function is as follows :
render() {
var { stockName, totalQuantity, show, close, children } = this.state
console.log('stockName ====> ', stockName) //undefined
console.log('totalQuantity ====> ', totalQuantity) //also undefined
...
What am I doing wrong? Please help me out here. Thank you.
Here's a link to the sandbox for the Modal to which this code belongs.
https://codesandbox.io/s/divine-bush-rquid

Remains state on React

I'm making an application now with react.
Set the calculation function, and sum: 0 specified by state remains at the beginning.
This is the actual operation.
https://www.useloom.com/share/43bd30dc0f0741f7b09c63a3728d2ba9
import React, { Fragment, Component } from "react";
class Account extends Component {
state = { sum: 0 };
input = event => {
this.setState({ input: event.target.value });
console.log(this.state.input);
};
Plus = () => {
this.setState({ sum: this.state.sum + this.state.input });
};
Main = () => {
this.setState({ sum: this.state.sum - this.state.input });
};
render() {
return (
<Fragment>
<h2>口座</h2>
<h3>
現在:
{this.state.sum}
</h3>
<input onChange={this.input} />
<br />
<button onClick={this.Plus}>収入</button>
<button onClick={this.Main}>支出</button>
</Fragment>
);
}
}
export default Account;
Please let me know the way to erase the leading zeros and why sum: 0 is left.
Thank you.
First, you are doing a string concatenation rather than a sum.
You need to get a number from your input, for example through + operator:
this.setState({ input: +(event.target.value) });
Then, when you want to set the state based on the previous state, you should use the setState method with a callback as argument, which provides you the previous state, like:
this.setState(prevState => ({ sum: prevState.sum + prevState.input }));
Then when you init your state, you should specify all the state:
state = { sum: 0, input: 0 };
So your final code should look something like:
import React, { Fragment, Component } from "react";
class Account extends Component {
state = { sum: 0, input: 0 };
input = event => {
this.setState({ input: +(event.target.value) });
console.log(this.state.input);
};
Plus = () => {
this.setState(prevState => ({ sum: prevState.sum + prevState.input }));
};
Main = () => {
this.setState(prevState => ({ sum: prevState.sum - prevState.input }));
};
render() {
return (
<Fragment>
<h2>口座</h2>
<h3>
現在:
{this.state.sum}
</h3>
<input value={this.state.input} onChange={this.input} />
<br />
<button onClick={this.Plus}>収入</button>
<button onClick={this.Main}>支出</button>
</Fragment>
);
}
}
export default Account;
Two things: your input is becoming uncontrolled, and you have to cast the value from string to number.
The first is a subtle issue that will be more helpful for more advanced app you'll made. You have to assign a value prop to your input, this way:
<input value={ this.state.input } onChange={this.input} />
Remeber also to assign an initial value to state.input, something like an empty string is fine.
The main problem you have is the casting of the input value to a string, you can do it with:
this.setState({ input: Number(event.target.value) })
One more thing, when you update state using value from the state itself, use the method notation instead the object one:
this.setState(prevState => ({ sum: prevState.sum + prevState.input }))

Categories

Resources