Saving the values in forms after leaving the page in reactjs - javascript

So I have a bunch of forms that the user can navigate back and forth from, but when he comes back the form should be as they left it, and all their values can be submitted at once.
I thought of keeping a large object, that temporarily stores the value for all the forms and is what is submitted by then end of it.
Whenever I return to the page, I just put that certain object in the value property.
The problem with this, is that once I return to the filled out form, I can't edit it anymore.
What can I do to make this happen? Also, I am willing to change it completely if there is a whole better way to do this.
Here is some relevant code with some comments explaining things:
// form3 only has to text fields, and form4 only has a file entry,
//which is why they aren't objects
//form1 and form2 on the other hand are huge and generate dynamically (I get
//all their fields through a get request and don't necessarily know
//how many there will be
export var formDataObject = {
form1:{},
form2:{},
form3:{
field1:"",
field2:""
},
form4:""
};
// for the next part I'll use form3 as an example since it is simple:
//The Form3 component is just a simple text input, I include the code
//of that too in case it's needed
export default class FullForm3 extends Component{
constructor(props){
super(props);
this.state ={
initial_field1: formDataObject.form3.field1,
initial_field2: formDataObject.form3.field2
}
}
render(){
var field1Value, field2Value;
if (this.state.initial_field1.length>0)
field1Value = formDataObject.form3.field1;
if (this.state.initial_field2.length>0)
field2Value = formDataObject.form3.field2;
return (
<div>
<Panel>
<Form3 controlId="field1Id" label={field1Label}
value={field1Value}/>
<br/><br/>
<Form3 controlId="field2Id" label={field2Label}
value={field2Value}/>
</div>
);
}
}
class Form3 extends Component{
constructor(props){
super(props);
this.state = {
value: formDataObject.form3,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const value = Object.assign({}, this.state.value);
value[e.target.id] = e.target.value;
this.setState({ value });
formDataObject.form3= this.state.value;
}
handleSubmit(e){
//things for submit (not important)
}
render(){
return (
<Form inline onSubmit={this.handleSubmit}>
<ControlLabel>{this.props.label}</ControlLabel>
<FormControl
type='text' label='Text'
value={this.props.value} onChange={this.handleChange}
/>
</FormGroup>
</Form>
)
}
}

I did it in a few steps:
Create controlled forms/inputs
OnInputChange save the formData for later use inside a callback from setState() (I used localStorage)
Get saved data in constructor(more readable in my example)/componentWillMount and setState of inputs to saved values
Now you can edit these inputs after refresh
Here you have full component with 2 inputs:
class SimpleInputs extends Component {
constructor(props) {
super(props);
if(localStorage.getItem('formData')) {
this.state = JSON.parse(localStorage.getItem('formData')); //in this case the state is just for input values
} else {
this.state = {
value1: '',
value2: ''
}
}
this.handleInput1Change = this.handleInput1Change.bind(this);
this.handleInput2Change = this.handleInput2Change.bind(this);
}
handleInput1Change(event) {
this.setState({ value1: event.target.value }, () => {
localStorage.setItem('formData', JSON.stringify(this.state));
});
}
handleInput2Change(event) {
this.setState({ value2: event.target.value }, () => {
localStorage.setItem('formData', JSON.stringify(this.state));
});
}
render() {
return(
<form>
<label>Name</label>
<input type="text" value={this.state.value1} onChange={this.handleInput1Change}/>
<label>Phone number</label>
<input type="text" value={this.state.value2} onChange={this.handleInput2Change}/>
</form>
);
}
}

Related

State is always one step behind (setState async problems) - React js

I have three components:
PageBuilder - is basically a form where the user adds a page name and selects some items.
PageList - stores all pages the user has created in state and renders that state as a list
PageUpdater - takes the form info from PageBuilder and adds it to PageList
The problem I'm having is that the state of each component is always one step behind. I realise that this is because setState is asynchronous but I'm not sure what's the best way to get around that. I've read a few possible solutions but I'm not sure how best to implement them in my setup. Can anyone advise?
Here is PageBuilder (I've cut it down for clarity):
constructor(props){
super(props);
this.state = {
pageTitle: '', pageDesc:'', items: [], id:''
};
}
updateTitle = (e) => {
this.setState({pageTitle: e.target.value});
}
updateDesc = (e) => {
this.setState({pageDesc: e.target.value});
}
addNewPage = () => {
let info = {...this.state};
this.props.callBack(info);
}
render() {
return (
<input className="pageTitleField" type="text" placeholder="Page Title"
value={this.state.pageTitle} onChange={this.updateTitle}></input>
<textarea className="pageDescField" placeholder="Page description..."
onChange={this.updateDesc}></textarea>
<button onClick={this.addNewPage}>New Page</button>
)
}
PageUpdater:
export class PageUpdater extends React.Component{
constructor(props){
super(props);
this.state={
data: ''
}
}
updatePageList = (pageAdded) =>{
this.setState({data:pageAdded});
console.log(this.state)
}
render(){
return(
<div>
<PageBuilder callBack={this.updatePageList} />
<PageList addToList={this.state.data} />
</div>
)}}
PageList:
export class PageList extends React.Component{
constructor(props){
super(props);
this.state = {pages:''}
}
componentWillReceiveProps(props) {
this.setState({pages: [...this.state.pages, this.props.addToList]})
}
getPages = () => {
var pages = []
for(var key in this.state.pages){
pages.push(this.state.pages[key].pageTitle)}
return pages // Return an array with the names
}
render(){
return(
<div>
{this.getPages().map((page, index) => <li key={index}>{page}
</li>)}
</div>
)}}
Inside of componentWillReceiveProps this.props refers to the previous version of props. But what you need is to use the latest version of props.
Instead of
componentWillReceiveProps(props) {
this.setState({pages: [...this.state.pages, this.props.addToList]})
}
You should write
componentWillReceiveProps(nextProps) {
this.setState({pages: [...this.state.pages, nextProps.addToList]}) // notice the difference this.props vs nextProps
}

Handle Input values when clicked React js

I was trying to handle changing of states whenever I type something inside the two text boxes and then when the user click the button, it will set the state to it's state and then console.log the current change state to the console.
Basically I have this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
catname: '',
catamt: 0
};
this.addBudget = this.addBudget.bind(this);
}
addBudget(e) {
e.preventDefault();
this.setState({
catname: e.target.value,
catamt: e.target.value
});
console.log('console log catname here.....', this.state.catname);
console.log('console log catamt here.....', this.state.catamt);
}
}
And then inside my component where the form is sitting:
import React from 'react';
export default class AddBudget extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="cat-input">
<input
type="text"
name="categoryname"
placeholder="Budget Category"
/>
<input
type="number"
name="categoryamount"
placeholder="Target Budget"
/>
</div>
<button onClick={this.addBudget}>+</button>
);
}
}
How do I pass along my input value to my function and console log the change of state?
Something more like that, I recommended using controlled input with react.
You can read more about it here https://reactjs.org/docs/forms.html
An example for you :) https://codesandbox.io/s/2486wxkn9n
First you need to keep track on the value with the state. Second with the form you can handle the submit. This way if a user click the button or press enter you can handle the submit method.
Inside the _handleChange method you receive the event. So this is the input change. If you console.log this value you can see he have the name, the name you pass in the input. This way you can use it as a key variable for your object. So one function for 2 :).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
catname: '',
catamt: 0
};
this.addBudget = this.addBudget.bind(this);
}
addBudget = (e) => {
e.preventDefault();
console.log('console log catname here.....', this.state.catname);
console.log('console log catamt here.....', this.state.catamt);
}
_handleChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<AddBudget handleChange={this._handleChange} addBudget={this.addBudget} />
)
}
}
export default class AddBudget extends React.Component {
render() {
return (
<div className="cat-input">
<form onSubmit={this.props.addBudget}>
<input
type="text"
name="catname"
onChange={this.props.handleChange}
placeholder="Budget Category"
/>
<input
type="number"
name="catamt"
placeholder="Target Budget"
onChange={this.props.handleChange}
/>
<button type="submit">+</button>
</form>
</div>
);
}
}

How to dynamically set State from Form input

I have 2 React parent/child components. The Child Component has a button that adds +1 to the previous state of the Parent Component, and a Form that triggers a handleChange function for the onChange event.
The Problem
From the Form input, I want to trigger a function that sets the State to the previous State, + the input in the Form.
For example, if I write 50 in input and hit submit I want the new state be 100
Here is a codesandbox: https://codesandbox.io/s/30mz2vvyo1
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 50
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState((prevState) => {
return { value: prevState.value + 1 }
});
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<div>
<Child value={this.state.value} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
</div>
)
}
}
class Child extends React.Component {
render() {
return (
<div>
<button onClick={this.props.handleChange}>Count + 1</button>
<div>{this.props.value}</div>
<form onSubmit={this.props.handleSubmit}>
<label>
Name:
<input type="text" onChange={this.props.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
The problem you are facing can be mitigated by;
You need to have two different variables in state. value, can hold your value. You also need to hold the current value of the input, let's call it inputNumber.
You need to provide an onClick function to your button. In said function, set your state in the following fashion;
Code:
this.setState({
value: this.state.value + this.state.inputNumber,
})
After doing these things, it should work as expected.
I have updated your codesandbox, you can take a look at it here.

Clearing value state in react

Using an api endpoint that holds the an an array of objects for specific crypto currency coins.
I created a form where users can type in a specific coin and hit submit and it will return the price. That coin will then check if its in an array of object in the api. If it's valid then I push that into the filtered results array in the constructor.
My first search query works, but when I do my second query search and hit the submit button, it fails and just reloads the page.
constructor() {
super();
this.state = {value: ''};
this.state = {coin: []};
this.state = {items: []};
this.state = {filteredResults: []};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
let coin = this.state.value;
this.findCoin(coin);
event.preventDefault();
}
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
componentDidMount() {
fetch(`https://api.coinmarketcap.com/v1/ticker/`)
.then((result)=> {
result.json()
.then(json => {
this.setState({items: json})
});
});
}
render() {
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<div> Price: $ {this.state.filteredResults.price_usd}
</div>
</div>
);
}
}
The problem in this method:
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
At line
this.setState({filteredResults: this.state.filteredResults[0]});
you are setting filteredResults (which is an array) to an object and on the second search the line
this.state.filteredResults.push(currency)
gives you an error, as filredResults being a string doesn't have push method.
And as you have event.preventDefault on the last line of handleSubmit method it's not executing because of previous error and form is submitting.
This method is mutating state, which circumvents React's state checking;
findCoin(id) {
this.state.items.forEach(function(currency){
if(currency.id === id) {
this.state.filteredResults.push(currency)
}
}, this);
this.setState({filteredResults: this.state.filteredResults[0]});
}
Use a method such as filter, that gives a new array reference:
const filtered = this.state.items.filter(ccy=> ccy.id === id);
this.setState({filteredResults: filtered[0]};
Also as one of the other posters has mentioned, declare the filterResults as an object (if you are only ever going to display one filtered result), as it changes from array to object.
this.state = {filteredResults: {}};

How to avoid duplicate event listener in react?

I have a form in react with many input components. I do not like that I have to write a new onChange handler method for every input component that I build. So I want to know how can I stop repeated code.
<Input
label={"Blog Name"}
hint={"e.g. 'The Blog'"}
type={"text"}
value={this.state.name}
onChange={this.handleInputChange.bind(this, "name")}
/>
<Input
label={"Blog Description"}
hint={"e.g. 'The Blog Description'"}
type={"text"}
value={this.state.desc}
onChange={this.handleInputChange.bind(this, "desc")}
/>
So instead of writing a new function I am reusing the same function and passing an extra value. Is this the right way to do it? How do other experienced people solve this problem.
If you want your parent component to maintain the state with the value of each input field present in 'Input' child components, then you can achieve this with a single change handler in the following way:
handleChange(id, value) {
this.setState({
[id]: value
});
}
where the id and value are obtained from the Input component.
Here is a demo: http://codepen.io/PiotrBerebecki/pen/rrJXjK and the full code:
class App extends React.Component {
constructor() {
super();
this.state = {
input1: null,
input2: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id, value) {
this.setState({
[id]: value
});
}
render() {
return (
<div>
<Input id="input1"
changeHandler={this.handleChange} />
<Input id="input2"
changeHandler={this.handleChange} />
<p>See input1 in parent: {this.state.input1}</p>
<p>See input2 in parent: {this.state.input2}</p>
</div>
);
}
}
class Input extends React.Component {
constructor() {
super();
this.state = {
userInput: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const enteredText = event.target.valuel
this.setState({
userInput: enteredText
}, this.props.changeHandler(this.props.id, enteredText));
}
render() {
return (
<input type="text"
placeholder="input1 here..."
value={this.state.userInput}
onChange={this.handleChange} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
You can try event delegation, just like the traditional ways.
That is, just bind a function to the parent form element, and listens to all the events bubbling up from the children input elments.

Categories