Gatsby only passing on checkbox value to Netlify forms - javascript

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 :)

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
)
})
}

state hooks is not updating state on change

I am building a simple blog app and I am trying to update title of the blog But it is not updating, it is just showing the current state.
I have tried many times by changing the method of setting state but it is still showing that error.
App.js
function BlogDetail() {
const [blogName, setBlogName] = useState("");
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
})
const handleChange = (e) => {
console.log(e.target.value)
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
}
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
return (
<div>
<form>
{blogName}
<input type="text" name="blogName" value={blogName} onChange={e => handleChange} />
<button type="submit" onClick={e => saveBlog(e)}>Save</button>
<form>
</div>
)
}
And When I update on change instead of updating on submit
onChange=(e => setBlogName(e.target.value))
Then it is showing
A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined
I have tried many times but it is still not working.
input requires a string as a value, but you are trying to pass an object:
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
instead pass a string:
setBlogName(e.target.value)
Also, you need to execute handleChange function and pass the event param.
onChange={e => handleChange(e)}
Edit:
Looked at it second time and it should be like this:
function BlogDetail() {
const [blogName, setBlogName] = useState("");
// without this you override state every time you press a key
useEffect(() => {
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
})
}, [])
const handleChange = (e) => {
// just use value here
setBlogName(e.target.value)
}
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
return (
<div>
<form>
{blogName}
{ /* remember to run the function */ }
<input type="text" name="blogName" value={blogName} onChange={e => handleChange()} />
<button type="submit" onClick={e => saveBlog(e)}>Save</button>
<form>
</div>
)
}
Besides the problem that within handleChange you need to pass an an string value to setBlogName you also need to wrap your axios fetch call in a useEffect.
The problem is that everytime you trigger a rerender while calling setBlogName you are calling your API point again and set the value back to the fetched value.
You should prevent that by doing the following ->
useEffect(() => {
axios.get("/api/blog_detail/70/").then(res => {
setBlogName(res.data[0].blog_name)
}), [])
Don't forget to install { useEffect } from 'react'.
And well of course update handleChange ->
const handleChange = (e) => {
const newBlogPostName = e.target.value
console.log(newBlogPostName)
setBlogName(newBlogPostName)
}
you have not any action in this method. where is the update state?
const saveBlog = (e) => {
// sending to API
console.log(blogName)
}
and in this method you change the string to an object
const handleChange = (e) => {
console.log(e.target.value)
setBlogName({
...blogName,
[e.target.name]: e.target.value
})
}
so the problem is that your function updates your state to an object and then you want to display that object(not a string property of that object) in the DOM. its wrong because you cant display objects in the DOM in react. in this case, you even get an error because you cant use spread operator on strings. you cant do something like this: ...("test")
const handleChange = (e) => {
console.log(e.target.value)
//the state value will be an object. its wrong. you even get an error
because of using spread operator on a string
setBlogName({
...blogName //this is a string at the init time,
[e.target.name]: e.target.value
})
}
so whats the solution?
you should update your state to a string or use a string property of the object.
something like this:
const handleChange = (e) => {
console.log(e.target.value)
setBlogName("string")
}
return (<>{blogName}</>)
thats it.

Visit each child in props.children and trigger a function

I want to be able to visit the children <Textfield> of my form <Form> upon submit.
In each child hook object, I also want to trigger a certain function (eg., validate_field). Not sure if this possible in hooks? I do not want to use ref/useRef and forwardRef is a blurred concept to me yet (if that's of any help).
My scenario is the form has been submitted while the user did not touch/update any of the textfields so no errors were collected yet. Upon form submit, I want each child to validate itself based on certain constraints.
I tried looking at useImperativeHandle too but looks like this will not work on props.children?
Updated working code in:
https://stackblitz.com/edit/react-ts-jfbetn
submit_form(evt){
props.children.map(child=>{
// hypothetical method i would like to trigger.
// this is what i want to achieve
child.validate_field() // this will return "is not a function" error
})
}
<Form onSubmit={(e)=>submit_form(e)}
<Textfield validations={['email']}>
<Textfield />
<Textfield />
</Form>
Form.js
function submit_form(event){
event.preventDefault();
if(props.onSubmit){
props.onSubmit()
}
}
export default function Form(props){
return (
<form onSubmit={(e)=>submit_form(e)}>
{props.children}
</form>
)
}
So the Textfield would look like this
…
const [value, setValue] = useState(null);
const [errors, setErrors) = useState([]);
function validate_field(){
let errors = []; // reset the error list
props.validations.map(validation => {
if(validation === 'email'){
if(!some_email_format_validator(value)){
errors.push('Invalid email format')
}
}
// other validations (eg., length, allowed characters, etc)
})
setErrors(errors)
}
export default function Textfield(props){
render (
<input onChange={(evt)=>setValue(evt.target.value)} />
{
errors.length > 0
? errors.map(error => {
return (
<span style={{color:'red'}}>{error}</span>
)
})
: null
}
)
}
I would recommend moving your validation logic up to the Form component and making your inputs controlled. This way you can manage the form state in the parent of the input fields and passing in their values and onChange function by mapping over your children with React.cloneElement.
I don't believe what you're trying to do will work because you are trying to map over the children prop which is not the same as mapping over say an array of instantiated child elements. That is to say they don't have state, so calling any method on them wouldn't be able to give you what you wanted.
You could use a complicated system of refs to keep the state in your child input elements, but I really don't recommend doing that as it would get hairy very fast and you can just solve the issue by moving state up to the parent.
simplified code with parent state:
const Form = ({ children }) => {
const [formState, setFormState] = useState(children.reduce((prev, curr) => ({ ...prev, [curr.inputId]: '' }), {}));
const validate = (inputValue, validator) => {}
const onSubmit = () => {
Object.entries(formState).forEach(([inputId, inputValue]) => {
validate(
inputValue,
children.filter(c => c.inputId === inputId)[0].validator
)
})
}
const setFieldValue = (value, inputId) => {
setFormState({ ...formState, [inputId]: value });
};
const childrenWithValues = children.map((child) =>
React.cloneElement(child, {
value: formState[child.inputId],
onChange: (e) => {
setFieldValue(e.target.value, child.inputId);
},
}),
);
return (
<form onSubmit={onSubmit}>
{...childrenWithValues}
</form>
)
};
const App = () =>
<Form>
<MyInput validator="email" inputId="foo"/>
<MyInput validator="email" inputId="foo"/>
<MyInput validator="password" inputId="foo"/>
</Form>
I still don't love passing in the validator as a prop to the child, as pulling that out of filtered children is kinda jank. Might want to consider some sort of state management or pre-determined input list.

How do I handle onChange of objects inside an array (React State)?

community,
I'm trying to build a table where its rows are rendered based on an array of objects:
this.state = {
tableContent: [
{
id: this.state.tableContent.length,
firstname:"",
lastname:""
},
...
]
Each row displays one object. (2 columns in this case, n rows)
Each cell is also an input field, so users can manipulate the table:
<tbody>
{this.state.tableContent.map((row) => {
return(
<tr key={row.id}>
<td><input value={row.firstname} onChange={this.handleFirstNameChange}> </input></td>
...
</tr>
)
})}
</tbody>
I want each cell / input field to display the change when the user changes the input value and as such the state. Because I'm prepopulating the input field with value={row.firstname} I need to define an onChange handler function that changes the state/value of the target object's firstnameproperty.
So how does my onChange handler function look like?
I tried using spreads, but to no avail so far...
Some thoughts:
Using the standard procedure doesn't work because I have a nested state (Array of objects):
handleChange = (event) => { this.setState({ firstname: event.target.value }) }
Trying to use the spread operator results in some weird mess as well:
(this code here is somewhat wrong: maybe you can fix it?)
handleFirstNameChange = (event) => {
const {tableContent} = {...this.state};
const currentState = tableContent;
const { firstname, value } = event.target;
currentState[0] = value;
this.setState({ tableContent: currentState});
}
I appreciate any help!
edit:
The code below seems to nearly work. (thanks #Nikhil ) However now, whenever the user types into the input field, every letter they type will replace the existing letter / value of 'row.firstname'. Also, state doesn't refresh automatically so only the last-typed letter would show up / persist. What I need the input field to have is a functionality just like any casual input field.
event.persist(); seems to be needed to keep the event value.
handleFirstNameChange = (id, event) => {
event.persist();
this.setState(prevState => {
tableContent : prevState.tableContent.forEach((row) => {
if(row.id === id) { row.firstname = event.target.value}
})
})
}
input looks like this:
onChange={(event) => this.handleWNRChange(row.id,event)}
I think something like this would work.
const handleFirstNameChange = (id, event) => {
event.preventDefault();
const {tableContext} = this.state;
const myRowIndex = tableContext.findIndex((row) => row.id === id);
tableContext[myRowIndex].firstname = event.target.value;
this.setState({ tableContext });
}
This should be all you need. Just assign this method to the onChange of the input element. Like so:
onChange={(event) => this.handleFirstNameChange(row.id, event)}
May be this will help
{this.state.tableContent.map((row, index) => {
return(
<tr key={row.firstname}>
<td><input value={row.firstname}
onChange={(event)=>this.handleFirstNameChange(event, index)> </input></td>
</tr>
)
})
}
handleFirstNameChange(event, index){
this.setState((prevState => {
tableContent : prevState.tableContent.forEach((row,i)=>{
if(index === i) row.firstname = event.target.value
})
})
}

Generic function in react with multiple state variables

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.

Categories

Resources