I'm creating an intake form where a piece of data can be input into the text field, or generated randomly with a button next to the field.
I want to do this for 3 fields on the form so I created a component
called <RandomDataButton />
I'm stuck with how to make sure the results of the calculation done by the button component update the value of the text box so that the form submission contains the generated data.
I don't fully understand the state propagation, but what I do understand is that the flow is one way, down the hierarchy of components.
So what I am attempting to do is have a choice of inputting some data in the text box, or generating some random data from a button (I'd like to reuse it in other ui creations)
Where I am stuck is how do I update the input field from the componenet that is lower in the hierarchy.
Do I pass the state to the randomizer button and then have it update a copy of state? Or am I totally off base with that approach?
App:
class App extends React.Component {
render(){
return (
<div>
<DataInputForm />
</div>
);
}
}
DataInputForm:
class DataInputForm extends React.Component{
state= {
projectname: '',
datasource: '',
data1: '',
data2: '',
data3: '',
};
handleSubmit = e => {
e.preventDefault();
console.log({
projectname: this.projectname.value,
datasource: this.datasource.value,
data1: this.data1.value,
data2: this.data2.value,
data3: this.data3.value,
});
}
handleChange = e => this.setState({[e.target.name]: e.target.value});
render(){
return(
<form className="ui form" onSubmit={this.handleSubmit}>
<div className="field">
<label htmlFor="projectname">Project Name: </label>
<input
type="text"
id="projectname"
name="projectname"
placeholder="Project Name"
ref={input => this.projectname = input}
/>
</div>
<div className="field">
<label htmlFor="datasource">Data Source: </label>
<input
type="text"
id="datrasource"
name="datasource"
placeholder="Data Source"
ref={input => this.datasource = input}
/>
</div>
<div className="field">
<label htmlFor="data1">Data 1: </label>
<input
type="number"
min="3"
max="18"
id="data1"
name="data1"
ref={input => this.data1 = input}
/>
<RandomDataButton buttonid={"data1button"} buttonname={"Data1"} />
</div>
<div className="field">
<label htmlFor="data2">Data 2: </label>
<input
type="number"
min="3"
max="18"
id="data2"
name="data2"
ref={input => this.data2 = input}
/>
<RandomDataButton buttonid={"data2button"} buttonname={"Data2"} />
</div>
<div className="field">
<label htmlFor="data3">Data 3: </label>
<input
type="number"
min="3"
max="18"
id="data3"
name="data3"
ref={input => this.data3 = input}
/>
<RandomDataButton buttonid={"data3button"} buttonname={"Data3"} />
</div>
<button className="ui button" type="submit">Create Data</button>
</form>
);
}
}
RandomDataButton:
const getRandom = max => Math.floor(Math.random() * Math.floor(max));
class RandomDataButton extends React.Component {
generateData(value){
var result, destination;
destination = value.toLowerCase();
result = getRandom(1000);
console.log("Generated " + result + " for range of " + value + "]: " + destination);
//this.state.{destination};
}
render(){
return(
<button id={this.props.buttonid} type="button" onClick={this.generateData.bind(null,this.props.buttonname)}>{this.props.buttonname}</button>
//<button id="strbutton" type="button">Generate</button>
);
}
}
Pass a function as prop to RandomDataButton. Define the function in DataInputForm and use it update the state in DataInputForm by calling the prop function from RandomDataButton whenever you need the update.
It seems you are working with multiple components, so while working with multiple components, it is highly recommended to use any central storage container, which would be useful to get your desired data in any components
Flux and redux both are tested architectures for data state management, you could use any of them, I would recommend using redux.
Here's a codesandbox for your reference: https://codesandbox.io/s/bold-frog-01ff2
This is effectively a continuation of Amala's suggestion.
You are correct, the hierarchy is one-way. Which means we should define a function in DataInputForm (lvl2) and pass it as a prop to RandomDataButton (lvl3). That function is bound to DataInputForm's execution context, and we want to update it's state so we can feed the new data back into each individual input.
For example:
createRandomText = (associatedField, value) => {
this.setState(
{
[associatedField]: value
},
() => console.log(this.state)
);
};
So to update the state correctly, we need to provide a field corresponding to the right input, and a value (the randomized value).
We pass in that function as a prop to RandomDataButton and use it for the onClick() handler.
class RandomDataButton extends React.Component {
generateData = () => {
let result = getRandom(1000);
this.props.createRandomText(this.props.matchingInput, result);
};
render() {
return (
<button
id={this.props.buttonid}
type="button"
onClick={this.generateData}
>
{this.props.buttonname}
</button>
//<button id="strbutton" type="button">Generate</button>
);
}
}
Additionally we need to provide another prop to the button component so we can call the above function correctly:
<RandomDataButton
buttonid={"data1button"}
buttonname={"Data1"}
createRandomText={this.createRandomText}
matchingInput={"data1"}
/>
See sandbox for full details :)
Related
i want to get value of first input tag in eduction [0] and value of second input tag in education[1]
education is array.
<input type="text" name="education" value={this.state.education[0]} onChange={this.handleChange}
class="form-control" />
<input type="text" name="education" value={this.state.education[1]} onChange={
this.handleChange()} class="form-control"/>
It's better to do it as follow (It allow you to dynamically create your inputs and also if you don't want dynamic input you can use the same technique as well)
constructor(props) {
super(props);
this.state = {
education: ["", ""] // I've added 2 items to create 2 inputs
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const education = [...this.state.education];
education[e.target.id] = e.target.value;
this.setState({
education: education
});
}
render() {
return (
<div>
{
this.state.education.map((item, index) => (
<input
id={index}
type="text"
name="education"
value={this.state.education[index]}
onChange={this.handleChange}
class="form-control"
/>
))
}
</div>
);
}
In your second input you (by mistake i think) added () after your this.handleChange function. This means this function will be called immediately and won't be called onChange.
I'm trying to clear a form in my react-redux component. I know that I should be able to clear them using setState() after a submit, but since I'm receiving all my data as props (via the redux store) is there an easy way to do this within the component itself?
class Postform extends Component {
constructor(props){
super(props)
this.state={
propertyName: ' enter name ',
footage: ' size in sqft ',
address: ' full address ',
price: ' $ 00.00 '
}
}
onChange =(e)=>{
this.setState({ [e.target.name] :e.target.value});
}
onSubmit = (e) =>{
e.preventDefault()
const newListing = {
propertyName: this.state.propertyName,
footage: this.state.footage,
address: this.state.address,
price: this.state.price
}
this.props.newProperty(newListing)
// my attempt to reset the state of the form (unsure how to accomplish this?)
this.setState({
propertyName: '',
footage: '',
address: '',
price: ''
})
};
render() {
return (
<div className="form">
<h2>Add Listing</h2>
<form onSubmit = {this.onSubmit}>
<div>
<label>your listing name</label><br/>
<input name="propertyName" type="text" onChange={this.onChange} placeholder={this.state.propertyName} />
</div>
<div>
<label>listing size </label><br/>
<input name="footage" onChange={this.onChange} placeholder={this.state.footage} />
</div>
<div>
<label>listing location </label><br/>
<input name="address" onChange={this.onChange} placeholder={this.state.address} />
</div>
<div>
<label>desired price </label><br/>
<input name="price" onChange={this.onChange} placeholder={this.state.price} />
</div>
<br/>
<button className="submitbtn" type="submit">Submit</button>
</form>
</div>
)
}
}
Postform.propTypes = {
newProperty: PropTypes.func.isRequired,
new: PropTypes.object
}
const mapStateToProps = state =>({
listings: state.listings.properties,
new: state.listings.newListing
});
export default connect(mapStateToProps, {newProperty})(Postform)
I checked around online and found a few solutions. I wanted to see if anyone could tell me based on my code if there would be a preferred way of achieving this?
Here all the different methods I found not sure which I should use based on my component though : https://redux-form.com/6.0.0-alpha.7/docs/faq/howtoclear.md/
Your approach seems valid to me, I would only do the job in a bit 'lazy' manner:
this.setState(Object.keys(this.state).forEach(key => this.state[key] = ''))
import { initialize, reset } from 'redux-form';
dispatch(initialize('formName', {})); // Clear form
// or
dispatch(reset('formName'));
I want to add some input lines by a click of a button and add it to state so I can send it to the server, but I'm not sure how to add it to the fetch method or even if it's added to state,
this is what i have so far:
export class AdminPage extends React.Component {
constructor(props){
super(props);
this.state = {
sendeEmail: '',
matrialeliste: [{
matrialer: '',
antal: '',
pris: ''}]
};
}
handleUserInput = (e) => {
if (["matrialer", "antal", "pris"].includes(e.target.className) ) {
let matrialeliste = [...this.state.matrialeliste]
//matrialeliste[e.target.dataset.id][e.target.className] = e.target.value
this.setState({ matrialeliste }, () => console.log(this.state.matrialeliste))
} else {
const name = e.target.name;
const value = e.target.value;
this.setState({[name]: value};
}
}
addMatrialeliste = (e) => {
this.setState((prevState) => ({
matrialeliste: [...prevState.matrialeliste, {matrialer:"", antal:"", pris:""}],
}));
}
onSubmitSignIn = (event) => {
event.preventDefault();
fetch(`${api.url}/form`, {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
sendeEmail: this.state.sendeEmail,
})
})
.then((response) => (response.json()))
.catch(error => console.log(error));
}
render(){
let {matrialeliste} = this.state;
return(
<div>
<div>
<h1>Arbejds seddel</h1>
<form>
<div>
<button type="button" onClick={this.addMatrialeliste}>
tilføj materialer
</button>
{
matrialeliste.map((val, idx) => {
return(
<div key={idx}>
<div>
<label htmlFor="matrialer">
Matrialeliste
</label>
<input name='matrialer' type="text" className='matrialer' onChange={this.handleUserInput} />
</div>
<div>
<label htmlFor="antal">
Antal
</label>
<input name='antal' type="number" className='antal' onChange={this.handleUserInput} />
</div>
<div>
<label htmlFor="pris">
Pris
</label>
<input name='pris' type="number" className='pris' onChange={this.handleUserInput} />
</div>
</div>)})}
<label htmlFor="email">
E-mail
</label>
<input name='email' type="email" onChange={e => this.handleUserInput} />
<button type="submit">Send som E-mail</button>
<div>
<button type="submit" disabled=this.state.formValid}>Create</button>
</div>
</div>
</form>
</div>
</div>
);
}
}
I can get to add extra lines, but I don't know how to add it to the fetch method.
I was thinking I could map it, but I'm still unsure how do that
Creating forms with plain React requires you to write each part of the process and for a complex state its become tough .so, my personal opinion is to go with Formik or React Hook Form as they cover most of the features.
In your case, I am assuming you want to sent whole state to fetch method .here is an example of your code which implemented with Formik library.
There is a field in which the static path of file formation is registered. I have made this field editable in case the user wants to change the path for the formation according to his desire.
<div className="w3-col l8 m8 s8">
<input
className="w3-input"
type="text"
name="file_name"
defaultValue={`${test_path}`}
disabled={false}
/>
</div>
Так же есть кнопка, при клике которой вызывается функция формирования файла по заданному пути
<Btn_enabled
func={() => create_file(`${test_path}.xmind`)}
name="Create file"
/>
How can I transfer the modified path by the user to the button?
You need to introduce a state for your input component.
And just reference it (by passing it through props, state manager, etc).
For example, input value state named value:
// With hooks
function InputValueToButton() {
const [value, setValue] = useState(testPath);
return (
<div className="App">
<input
onChange={e => setValue(e.target.value)}
type="text"
value={value}
/>
<Btn_enabled func={() => create_file(`${value}.xmind`)}/>
</div>
);
}
If you are using a version of React that does not support hooks, you can achieve the same thing using classes:
// React classes
class InputValueToButton extends React.Component {
state = {
value: testPath
};
render() {
return (
<div className="App">
<input
onChange={e => this.setState({ value: e.target.value })}
type="text"
value={this.state.value}
/>
<button>{this.state.value}</button>
</div>
);
}
}
Problem
I have a list of people. I want to:
Select a user to edit by clicking on their name.
Edit that user's information, so I can click the submit button and update the list.
If I click on a different name, I want to switch to that person's information without having to deliberately close the form first.
Everything works until #3. When I click on another person, the form, itself, does NOT update.
My Code
Update Component for the update form:
const UpdateForm = ({ updatePerson, personToUpdate, handleInputChange }) => {
let _name, _city, _age, _id;
const submit = (e) => {
e.preventDefault();
updatePerson({
name: _name.value,
city: _city.value,
age: _age.value,
_id: _id.value
});
};
return (
<div>
<form onSubmit={submit}>
<h3>Update Person</h3>
<label htmlFor="_id">Some Unique ID: </label>
<input type="text" name="_id" ref={input => _id = input} id="_id" defaultValue={personToUpdate._id} onChange={input => handleInputChange(personToUpdate)} required />
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" ref={input => _name = input} id="name" defaultValue={personToUpdate.name} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" ref={input => _city = input} id="city" defaultValue={personToUpdate.city} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" ref={input => _age = input} id="age" defaultValue={personToUpdate.age} onChange={input => handleInputChange(personToUpdate)} />
<br />
<input type="submit" value="Submit" />
</form>
</div>
);
};
export default UpdateForm;
Relevant parts of Person Component:
class Person extends Component {
nameClick() {
if (this.props.person._id !== this.props.personToUpdate._id) {
this.props.setForUpdate(this.props.person);
this.forceUpdate();
}
else {
this.props.toggleUpdatePersonPanel();
}
}
render() {
return (
<div>
<span onClick={this.nameClick}>
{this.props.person.name} ({this.props.person.age})
</span>
</div>
);
}
}
export default Person;
Relevant parts of PeopleList, which holds Persons:
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person) => {
return <Person
key={person._id}
person={person}
updatePersonPanel={this.props.updatePersonPanel}
setForUpdate={this.props.setForUpdate}
personToUpdate={this.props.personToUpdate}
/>;
})}
</div>
);
}
} // end class
export default PeopleList;
Form Reducer, with just the relevant actions:
export default function formReducer(state = initialState.form, action) {
let filteredPeople;
switch (action.type) {
case TOGGLE_UPDATE_PANEL:
return Object.assign({}, state, { updatePersonPanel: false }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}});
case SET_FOR_UPDATE:
return Object.assign({}, state, { personToUpdate: action.person }, { updatePersonPanel: true });
case UPDATE_RECORD:
filteredPeople = state.people.filter((person) => {
return person._id === action.person._id ? false : true;
}); // end filter
return Object.assign({}, state, { people: [ ...filteredPeople, action.person] }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}}, { updatePersonPanel: false });
case HANDLE_INPUT_CHANGE:
return Object.assign({}, state, { personToUpdate: action.person });
default:
return state;
}
}
The relevant parts of my Initial State file:
form: {
people: [
{
_id: "adfpnu64",
name: "Johnny",
city: "Bobville",
age: 22
},
{
_id: "adf2pnu6",
name: "Renee",
city: "Juro",
age: 21
},
{
_id: "ad3fpnu",
name: "Lipstasch",
city: "Bobville",
age: 45
}
],
updatePersonPanel: false,
personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
},
}
Attempts at a Solution( so far)
I have attempted to make the component a completely controlled component, by switching the form attribute to value instead of defaultValue. When I do this, the names switch just fine, but the form becomes unchangeable and useless.
My Questions
Almost all of the solutions to these kind of issues either recommend using redux-form or supply two-way binding solutions that work fine in React without reduce. I want to know how to do this with Redux without using redux-form or anything extra if possible. Is there a way to resolve this without touching lifecycle methods?
Conclusion (For now)
Well, for now, I settled for making my form uncontrolled and used some classic Js DOM methods and a lifecycle method to control the form. For whatever reason, once I employed some of the answer suggestions my browser ate up my CPU and crashed, presumably because there was some kind of infinite loop. If anyone has some further recommendations, I'd really appreciate it. For now I settle for this:
class UpdateForm extends Component {
constructor(props){
super(props);
this.submit = this.submit.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.personToUpdate._id !== this.props.personToUpdate._id) {
document.getElementById("name").value = nextProps.personToUpdate.name;
document.getElementById("age").value = nextProps.personToUpdate.age;
document.getElementById("city").value = nextProps.personToUpdate.city;
}
}
submit(e) {
e.preventDefault();
this.props.updatePerson({
name: document.getElementById("name").value,
city: document.getElementById("city").value,
age: document.getElementById("age").value,
_id: this.props.personToUpdate._id
});
}
render() {
return (
<div>
<form onSubmit={this.submit}>
<h3>Update Person</h3>
Unique ID: {this.props.personToUpdate._id}
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" id="name" ref="name" defaultValue={this.props.personToUpdate.name} required />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" id="city" ref="city" defaultValue={this.props.personToUpdate.city} required />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" id="age" ref="age" defaultValue={this.props.personToUpdate.age} required />
<br />
<input type="submit" value="Update" />
</form>
</div>
);
}
} // end class
export default UpdateForm;
I'll be soon exploring redux-form because it is evident that forms as inputs and outputs are a wonky business. For now, my little app works.
Yes there is and you are on the right path. The way is to use value instead of defaultValue but you have to read the value from a state and then use the onChange handler to modify the state.
Something like
this.state = {inputText:''}
Then in the input field
<input value={this.state.inputText} onChange={this.handleChange}/>
And the handleChange function will be
handleChange(event){
this.setState({inputText:event.target.value})
}
Remember to bind the handleChange event in the constructor so you can pass it as this.handleChange in the input field's onChange prop.
Something like this
this.handleChange = this.handleChange.bind(this)
https://facebook.github.io/react/docs/forms.html - Here are the official docs regarding it
Also if you want to do it in redux the same sort of logic applies where this will be the input field
<input value={this.props.inputText} onChange={this.props.handleChange}/>
where inputText and handleChange are redux state and action respectively passed to the component via props
For your case I guess it has to be something like where you are 'reading' values from the people array and the action bound to the onChange modifies that value in the people array in the state.
<--EDIT-->
How it can be done for the case in point. Pass the people in the redux state as a people prop to the component. Pass an action changePeople(newPeople) to the component as a prop which takes an argument newPeople and changes the people in the redux state to have the value newPeople. Since people is nested in form you'll have to do some Object.assign etc to modify the state.
Now in the component using the people props populate the checkboxes using a map function. The map function takes a second parameter index so for each checkbox have a function which sets the local state variable currentPerson to the value of the index
this.props.people.map((person,index) =>
return <Checkbox onClick={()=>this.setState(currentPerson:index)}/>
)
So everytime you click on a checkbox the currentPerson points to the corresponding index.
Now the input fields can be
<input value={this.props.people[this.state.currentPerson].name} onChange={this.handleChange.bind(this,'name')}/>
This is for the 'name' input field. It reads from the currentPerson index of the people array which has been passed down as a prop.
This is how the handleChange will be like
handleChange(property,event){
const newPeople = [
...this.props.people.slice(0, this.state.currentPerson),
Object.assign({}, this.props.people[this.state.currentPerson], {
[property]: event.target.value
}),
...this.props.people.slice(this.state.currentPerson + 1)
]
this.props.changePeople(newPeople)
}
The handleChange takes a property (so you don't have to write separate handlers for each input field). The newPeople basically modifies the element at current index this.state.currentPerson in the people passed from props (ES6 syntax being used here. If you have doubts do ask). Then it is dispatched using the changePeople action which was also passed as props.