Can I put a form input in a react component? - reactjs

Usually my forms are super long, I would like to use them as components in separate files, I tried doing so but I can no longer modify the values, I mean there's two steps in this config,
first I need to pass initial values from the API fetch request, I used props like demonstrated below :
// in parent
<InvoiceDetails {...this.state} />
// in component
...
render() {
let { invoicenumber, ponumber, invoicedate, paymentdue, loading } = this.props;
return (
<div>
{!loading ?
<Form>
<Input fluid value={invoicenumber}
type="text" onChange={this.handleForm}
placeholder="Invoice number" name="invoicenumberEdit" />
<DateInput
autoComplete="off"
name="invoicedate"
placeholder="Invoice date"
dateFormat='MMMM Do YYYY'
value={invoicedate}
clearable icon={false}
clearIcon={<Icon name="remove" color="black" />}
onChange={this.handleInvoiceDate}
/>
...
The functions that update those inputs are changing the parent state, so I couldn't move them to the component file because that would be two separate states.
handleInvoiceDate = (event, { name, value }) => {
if (this.state.hasOwnProperty(name)) {
this.setState({ [name]: value });
}
}
handleForm = e => {
this.setState({ [e.target.name]: e.target.value });
};
I don't use context, redux or anything like that. How can I solve this if possible?

In your parent, make your handler a pure set state like this
handleInputChange = (name, value) => {
this.setState({ [name]: value });
};
then pass your handler as props like this
<InvoiceDetails {...this.state} handleInputChange={this.handleInputChange} />
then in your component, add these functions to your code
handleInvoiceDate = (event, { name, value }) => {
if (this.state.hasOwnProperty(name)) {
this.props.handleInputChange(name, value);
}
}
handleForm = e => {
this.props.handleInputChange(e.target.name, e.target.value);
};

Just pass your function's from parent to child component as a props like,
<InvoiceDetails {...this.state} handleForm={this.handleForm} handleInvoiceDate={this.handleInvoiceDate}/>
Function's in parent,
handleInvoiceDate = (name, value ) => {
if (this.state.hasOwnProperty(name)) {
this.setState({ [name]: value });
}
}
handleForm = (name, value) => {
this.setState({ [name]: value });
};
In your component call these function's like,
<Input fluid value={invoicenumber}
type="text" onChange={(e)=>this.props.handleForm(e.target.name,e.target.value)}
placeholder="Invoice number" name="invoicenumberEdit"
/>
<DateInput
...
onChange={(e)=>this.props.handleInvoiceDate(e.target.name,e.target.value)}
/>

Related

How to update property state without using this

I'm updating one of my state properties without using the setter and using a normal variable assignment in js, that causes me the problem that the property state only updates once, so when i want to check a checkbox more than once the state in app does not update correctly, i think that the problem causing it is that i'm messing up that property assignment.
I searched for how to update a property of a state without overwritting the whole object and every post i find answering that question is using this.setState instead of declaring a state and using the declared setter method and i don't know how to adapt that this update to my code.
For example, the top post called "How to update nested state properties in React" declares his state like this:
this.state = {
someProperty: {
flag:true
}
}
While i'm declaring my state like this:
const [EditedTask, setEditedTask] = useState({
name: props.task.name,
completed: props.task.completed,
_id: props.task._id,
});
const { name, completed } = EditedTask;
And the top answer to that question is:
this.setState(prevState => ({
...prevState,
someProperty: {
...prevState.someProperty,
someOtherProperty: {
...prevState.someProperty.someOtherProperty,
anotherProperty: {
...prevState.someProperty.someOtherProperty.anotherProperty,
flag: false
}
}
}
}))
In this solution, with which object should i replace the this in the this.setState? Or the prevState param?
In my code i need to update the checkbox/ completed state, with this solution i'm only receiving the object EditedTask in my app component when i edit the checkbox once, if i check it more than once the state doesn't update in app, meanwhile if i edit the name the state updates in app correctly with both the name and completed propertys.
import React, { useState } from "react";
import "../App.css";
const EditTask = (props) => {
const [EditedTask, setEditedTask] = useState({
name: props.task.name,
completed: props.task.completed,
_id: props.task._id,
});
const { name, completed } = EditedTask;
const onChange = (e) => {
setEditedTask({
...EditedTask,
[e.target.name]: e.target.value,
});
};
const onSubmit = (e) => {
e.preventDefault();
setEditedTask(EditedTask);
props.saveEditedTask(EditedTask);
props.changeEdit(false);
};
return (
<>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Task Id: ${props.task._id}</label>
<div className="inputEdit">
<input
type="text"
placeholder={props.msg}
name="name"
onChange={onChange}
value={name}
/>
</div>
</div>
<div className="form-check">
<input
className="form-check-input"
type="checkbox"
defaultChecked={completed}
value={completed}
onChange={() => (EditedTask.completed = !EditedTask.completed)}
name="completed"
/>
<label className="form-check-label">Completed</label>
</div>
<div className="submit">
<button type="submit">Edit</button>
</div>
</form>
</>
);
};
export default EditTask;
I tried replacing the onChange={() => (EditedTask.completed = !EditedTask.completed)} like the top answer/ my onChange for the name value with something like this but it doesn't update the completed value
const updateCompleted = (e) => {
setEditedTask({
...EditedTask,
[e.target.name]: !e.target.value,
});
};
You can try something like this in your setter functions.
const updateCompleted = (e) => {
setEditedTask((currentState) => ({
...currentState,
[e.target.name]: !e.target.value
})
)};
This solution will give you your previous state, which your can use to update your state.

React collect child component data on some event from the parent component

In react best practice is Data flow from parent to child, event's will be passed from child to parent.
In this UI we have a parent component which contains 2 child components with forms. We now have to gather data from child components when the user clicks on the submit button in the parent.
Possible solutions (Bad solutions | anti pattern):
Pass ref from child and trigger child method from parent to collect data (Accessing child method from parent not a good idea)
<Parent>
onFormData(data) {
//here collect data from child
}
onSubmit() {
//Trigger a method onData in child
aRef.collectData()
}
<child-A ref={aRef} onData={onFormData}></Child-A>
<Child-B ref={bRef} onData={onFormData}></Child-B>
<button onClick={onSubmit}>Submit</Submit>
</Parent>
Bind a props to child, on submit click change value of prop with dummy value. Observe same prop in useEffect hook ( Really bad solution)
<Parent>
onFormData(data) {
//here collect data from child
}
onSubmit() {
//update randomValue, which will be observed in useEffect that calls onData method from child
randomValue = Math.random();
}
<child-A triggerSubmit={randomValue} onData={onFormData}></Child-A>
<Child-B triggerSubmit={randomValue} onData={onFormData}></Child-B>
<button onClick={onSubmit}>Submit</Submit>
</Parent>
Is there any other best approach for handling these scenario? How to a avoid anti pattern for this UI?
What I usually do is to "lift the state up" to the parent. This means I would not break the natural React flow, that is passing props from the parent to the children. Following your example, I would put ALL the logic in the parent (submit function, form state, etc)
Const Parent = () => {
const [formData, setFormData] = useState({})
const onSubmitForm = () => {
// send formData to somewhere
}
return (
<ChildrenForm onChange={setFormData} formData={formData} />
<button onSubmit={() => onSubmitForm()}>my button</button>
)
}
Now I would use the onChange function inside the Children to update the formData everytime an input in the ChildrenForm changes. So all my state will be in the parent and I don't need to worry about having to pass everything up, from children to parent (antipattern as you mentioned)
there is the third option (which is the standard way): you don't collect data, you pass your formData and setFormData to each Child as props. using lift state approach.
Each Child populates its input values with formData and uses setFormData to update formData that lives on Parent. Finally, at on submit you only have to set formDatato you request call.
below an example:
const ChildA = ({ formData, setFormData }) => {
const { name, age } = formData
const onChange = ({ target: { name, value } }) => { // destructuring 'name' and 'value'
setFormData(formData => ({ ...formData, [name]: value })) // spread formData, update field with 'name' key
}
return (
<>
<label>Name<input type="text" onChange={onChange} name="name" value={name} /></label>
<label>Age<input type="number" onChange={onChange} name="age" value={age} /></label>
</>
);
}
const ChildB = ({ formData, setFormData }) => {
const { email, acceptTerms } = formData
const onChange = ({ target: { name, value } }) => {
setFormData(formData => ({ ...formData, [name]: value }))
}
const onClick = ({ target: { name, checked } }) => {
setFormData(formData => ({ ...formData, [name]: checked }))
}
return (
<>
<label>email<input type="email" onChange={onChange} name="email" value={email} /></label>
<label>Accept Terms<input type="checkbox" onChange={onClick} name="acceptTerms" checked={acceptTerms} /></label>
</>
);
}
const Parent = () => {
// used one formData. you could break down into more if you prefer
const [formData, setFormData] = useState({ name: '', age: null, acceptTerms: false, email: undefined })
const onSubmit = (e) => {
e.preventDefault()
// here you implement logic to submit form
console.log(formData)
}
return (
<>
<ChildA formData={formData} setFormData={setFormData} />
<ChildB formData={formData} setFormData={setFormData} />
<button type="submit" onClick={onSubmit}>Submit</button>
</>
);
}
Approach 2) with random is Not Really So Bad! And only this one preserves encapsulation not involving DOM/withRef round trip. Suggested Up State way leads to hard coded dependency and undermines whole idea of re-usability. The only thing I have to mention is: code as it was typed above will not work - because usual variables will not trigger. Right way to make it like that:
// Parent component
let [randomValue, setRandomValue] = React.useState(Math.random());
const onSubmit = () => {
setRandomValue(Math.random());
console.log("click");
}
const onFormData = data => {
console.log(`${data}`);
}
...
<Child triggerSubmit={randomValue} onData={onFormData}/>
// Child component
useEffect(() => {
console.log("child id here");
prop.onData(`Hey Ho! Lets go!`)
}, [prop.triggerSubmit]);
At least this works for me!

Can't access e.target.name

I have an application with different inputs. I want to access e.target.name of Switch, but i get undefined. For getting this i did:
const onChange = (name,e) => {
console.log(e.target.name)
}
and
<Switch defaultChecked onChange={e => onChange("test", e)} />
link to codesanbox: https://codesandbox.io/s/basic-usage-ant-design-demo-o3kro?file=/index.js:1198-1268
How to access Switch with e.target.name?
According to the docs(https://ant.design/components/switch/), I can see onChange takes the following format:
Function(checked: boolean, event: Event)
So In your code, you could pass the name attribute to your switch and access its current state(i.e checked) and also its event properties
const onChange = (checked, event) => {
console.log(checked, event.currentTarget.name);
};
<Switch defaultChecked name="test" onChange={onChange} />
The Switch component returns only trueor false.
If you do:
const onChange = (name,e) => {
console.log(e);
}
you can verify this.
If you want to acess the name "test" passed as argument, you just acess name parameter:
const onChange = (name,e) => {
console.log(name);
console.log(e);
}
**the switch return a boolean value on change **
const onChange = (name,checked) => {
console.log(checked)
}
and
<Switch defaultChecked onChange={checked => onChange("test", checked)} />

Setstate not updating in CustomInput type='checkbox' handler

This is the render method, how i am calling the handler and setting the reactstrap checkbox.
this.state.dishes.map(
(dish, i) => (
<div key={i}>
<CustomInput
type="checkbox"
id={i}
label={<strong>Dish Ready</strong>}
value={dish.ready}
checked={dish.ready}
onClick={e => this.onDishReady(i, e)}
/>
</div>))
The handler for the onClick listener, I've tried with onchange as well but it apears that onchange doesnt do anything, idk why?
onDishReady = (id, e) => {
console.log(e.target.value)
var tempArray = this.state.dishes.map((dish, i) => {
if (i === id) {
var temp = dish;
temp.ready = !e.target.value
return temp
}
else {
return dish
}
})
console.log(tempArray)
this.setState({
dishes: tempArray
});
}
The event.target.value isn't the "toggled" value of an input checkbox, but rather event.target.checked is.
onDishReady = index => e => {
const { checked } = e.target;
this.setState(prevState => {
const newDishes = [...prevState.dishes]; // spread in previous state
newDishes[index].ready = checked; // update index
return { dishes: newDishes };
});
};
The rendered CustomInput reduces to
<CustomInput
checked={dish.ready}
id={i}
label={<strong>DishReady</strong>}
onChange={this.onDishReady(i)}
type="checkbox"
/>
No need to pass in a value prop since it never changes.
Note: Although an onClick handler does appear to work, semantically it isn't quite the correct event, you want the logic to respond to the checked value of the checkbox changing versus a user clicking on its element.
You can do it this way:
this.setState(function (state) {
const dishes = [...state.dishes];
dishes[id].ready = !e.target.value;
return dishes;
});

e.target.value is undefined while adding dynamic input data in react

I need to add an input box (input-0, input-1...) each time a button is clicked.Following is the relevant code.
// State
this.state = {
newText: {}
};
// Code to add dynamic input text
addInputText = () => {
let dynamicTextsNo = Object.keys(this.state.newText).length;
let newInputId = dynamicTextsNo+1;
let dynamicTextArr = this.state.newText;
let newTextId = 'input-'+dynamicTextsNo;
dynamicTextArr[newTextId] = '';
let newState = { ...this.state, newText: dynamicTextArr }
this.setState( newState );
}
// Code to render dynamic input text.
dynamicTextArea = () => {
return Object.keys(this.state.newText).map( ( key ) => {
return ( <InputGroup key={key} borderType='underline'>
<Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput}/>
</InputGroup>
);
});
}
// Render function
render() {
return <View>{this.dynamicTextArea()}</View>
}
// handle input
changeInput = (e) => {
console.log( e.target.value ); // **this comes out to be undefined.**
}
Why is e.target.value in changeInput function undefined?
P.S. Jsfiddle link of the full code: https://jsfiddle.net/johnnash03/9by9qyct/1/
Unlike with the browser text input element, the event argument passed to React Native TextInput.onChange callback does not have a property called target.
Instead, use
<TextInput
onChange={(event) => event.nativeEvent.text}
/>
or
<TextInput
onChangeText={(text) => text}
/>
You must use bind like so <Input placeholder='Type your text here' value={this.state.newText[key]} onChange={this.changeInput.bind(this)}/>

Resources