onChange event doesn't fire on first input - reactjs

I'm new to React and I've got this registration (parent component) where it has an inventory (child component) and I'm changing the state of the inventory once the user inputs the quantity of the item that they hold so I can register them
But onChange seems to be delayed, I input 4 water bottles for example, and it console logs me the default value, only when I input the amount for another item like food it displays me the 4 water bottles and 0 food :(
This is what I'm working with...
Child component:
import React from "react";
import { Col, Row, FormGroup, Label, Input } from "reactstrap";
import waterIcon from "./../assets/water.png";
import soupIcon from "./../assets/food.png";
import medsIcon from "./../assets/aid.png";
import weaponIcon from "./../assets/gun.png";
function Inventory({ onSubmitInventory, currentInventory }) {
const onChangeWater = (e) => {
const newInventory = { ...currentInventory, water: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeSoup = (e) => {
const newInventory = { ...currentInventory, soup: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeMeds = (e) => {
const newInventory = { ...currentInventory, meds: e.target.value };
onSubmitInventory(newInventory);
};
const onChangeWeapon = (e) => {
const newInventory = { ...currentInventory, weapon: e.target.value };
onSubmitInventory(newInventory);
};
return (
<FormGroup>
<Row className="justify-content-center text-center border-top pt-3">
<Col xs="3">
<img src={waterIcon} alt="Water" />
<Label for="inventoryWater">Fiji Water:</Label>
<Input
type="number"
name="inventoryWater"
id="inventoryWater"
placeholder="Enter amount..."
onChange={onChangeWater}
/>
</Col>
<Col xs="3">
<img src={soupIcon} alt="Soup" />
<Label for="inventorySoup">Campbell Soup:</Label>
<Input
type="number"
name="inventorySoup"
id="inventorySoup"
placeholder="Enter amount..."
onChange={onChangeSoup}
/>
</Col>
<Col xs="3">
<img src={medsIcon} alt="Aid" />
<Label for="inventoryMeds">First Aid Pouch:</Label>
<Input
type="number"
name="inventoryMeds"
id="inventoryMeds"
placeholder="Enter amount..."
onChange={onChangeMeds}
/>
</Col>
<Col xs="3">
<img
className="d-block"
style={{ margin: "0 auto" }}
src={weaponIcon}
alt="Gun"
/>
<Label for="inventoryWeapon">AK47:</Label>
<Input
type="number"
name="inventoryWeapon"
id="inventoryWeapon"
placeholder="Enter amount..."
onChange={onChangeWeapon}
/>
</Col>
</Row>
</FormGroup>
);
}
export default Inventory;
Parent component:
import React, { useState } from "react";
import { Col, Row, Button, Form, Label, Input } from "reactstrap";
import { useForm } from "react-hook-form";
import Inventory from "./Inventory";
import MapContainer from "./MapContainer";
function Register() {
const [lonlat, setLonlat] = useState("");
const onMarkerChange = (lonlat) => {
setLonlat(lonlat);
};
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
const onSubmitInventory = (newInventory) => {
setInventory(newInventory);
console.log(inventory);
};
const { register, handleSubmit, errors } = useForm();
const onSubmit = (data) => {
console.log(inventory);
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: data.personName }),
};
console.log(data);
};
return (
<Form
id="registerForm"
name="registerForm"
onSubmit={handleSubmit(onSubmit)}
>
<h4>New Person</h4>
<Row className="border-top pt-4">
<Col xs="8">
<Label for="personName">Your name:</Label>
<Input
className="gray-input"
type="text"
name="name"
id="personName"
placeholder="Enter your name here..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personAge">Age:</Label>
<Input
className="gray-input"
type="number"
name="age"
id="personAge"
placeholder="Enter age..."
innerRef={register}
/>
</Col>
<Col xs="2" className="text-center">
<Label for="personGender">Gender:</Label>
<Input
type="select"
name="gender"
id="personGender"
innerRef={register}
>
<option defaultValue disabled>
-
</option>
<option>F</option>
<option>M</option>
</Input>
</Col>
</Row>
<Row>
<Col xs="12">
<Input
hidden
id="personLatLon"
name="personLatLon"
type="text"
defaultValue={lonlat}
innerRef={register}
/>
<MapContainer onMarkerChange={onMarkerChange} />
</Col>
</Row>
<h4>Inventory</h4>
<Inventory
onSubmitInventory={onSubmitInventory}
currentInventory={inventory}
/>
<Button
outline
color="secondary"
className="mt-2"
type="submit"
form="registerForm"
>
Submit
</Button>
</Form>
);
}
export default Register;
EDIT: Updated code with tips from answers, still facing the same problem :(

The only problem I see with your code is that you are trying to pass a second argument to setInventory. I don't believe this works with hooks like it did with class components. When I attempt to type that in typescript it throws an instant error.
Just pass the actual data you are trying to send and then call onSubmitInventory(inventory) for example:
const onChangeWeapon = (e) => {
const newInventory = {...inventory, weapon: e.target.value};
setInventory(newInventory);
onSubmitInventory(newInventory);
};
If you have to wait for the next render to call onSubmitInventory, it should be a useEffect in the parent. My next question would be why the parent needs the state and the child also has it as a state? Possibly it should be lifted up. But I'm not sure of that without seeing the parent.
Edit: Is your problem just with the console.log? If you run
const [state, setState] = useState(true);
// ...
setState(false);
console.log(state); // outputs true.
The value of state does not change until the next render. Calling setState will cause a new render, but until that happens, the old value is there. Add a console.log of state in the parent just in the body like here
const [inventory, setInventory] = useState({
water: 0,
soup: 0,
meds: 0,
weapon: 0,
});
console.log('I just rendered, inventory: ', inventory);
You'll see it updates!
The fact that the value of state does not change can bite you in this case:
const [state, setState] = useState(true); // closes over this state!
// ...
setState(!state); // closes over the state value above
setState(!state); // closes over the same state value above
// state will be false on next render
So I use this form if there is any possibility I call setState twice before a render:
const [state, setState] = useState(true);
// ...
setState(state => !state);
setState(state => !state);
// state will be true on next render

Inside your parent component's onSubmitInventory you have named the argument inventory but that already exists in the parent's scope. I suggest renaming that newInventory to be clear about which you are referencing.
Also, it seems like you are keeping track of inventory in both the parent and child's state. Keep it in the parent and pass it down to the child as a prop/props.

Related

Can't type in react input field

I have a simple form with an input field that I can't type on. I first thought the problem was with the onChange or the value props that were setting the input to readonly, but the fact is that I cant type with the browser suggestions and the state updates perfectly (See gif here) it's just that I won't let me type with the keyboard, even after reloading the page.
I also have a Login page that works perfectly except when I log out and redirect back to that page, it won't work until I reload the page, now it will work.
<input
value={name}
onChange={handleChange}
name="name"
/>
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
Weird thing is that it's in like a readonly state but when I use browser suggestions it works and updates the state.
Here is the whole component:
import React, { useEffect, useState } from 'react';
import { useForm } from '../../utils/useForm';
import { CubeType } from '../../interfaces';
//import useStore from '../store/Store';
import { Modal, Button, Row, Col, FormGroup, FormLabel, FormControl } from 'react-bootstrap';
type Props = {
show: Boolean,
onClose: () => void,
cubeTypes: CubeType[]
};
const ModalTimelist = (props: Props) => {
//const store = useStore();
const [values, handleChangee] = useForm({ cubeType: 1, name: '' });
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
useEffect(() => {
const modal = document.getElementsByClassName('modal')[0];
if(modal) modal.removeAttribute('tabindex');
}, [props.show]);
return (
<>
<Modal show={props.show} onHide={ props.onClose }>
<Modal.Header>
<Modal.Title>Timelist { name }</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col md="3">
<FormGroup>
<FormLabel>Cube Type</FormLabel>
<select
value={values.cubeType}
onChange={ handleChangee }
className="form-select"
name="cubeType"
>
{props.cubeTypes.map((it, idx) => {
return (<option value={ idx } key={"cube"+idx}>{it.name}</option>);
}) }
</select>
</FormGroup>
</Col>
<Col md="9">
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
value={name}
onChange={handleChange}
name="name"
/>
</FormGroup>
</Col>
</Row>
</Modal.Body>
<Modal.Footer>
<Button variant="success" onClick={() => props.onClose()}>
Save
</Button>
<Button variant="outline-danger" onClick={() => props.onClose()}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default ModalTimelist;
value of input must be the state value otherwise it will not change use this code
const App = () => {
const [name,setName] = useState("")
const handle = ({target:{value}}) => setName(value)
return <input
value={name}
onChange={handle}
name="name"
/>
}
Use a debounce for setting name on state.
Example:
const handleChange = (e:any) => {
debounce(() => { setName(e.target.value) }, 300);
}
I tried the code and it works fine I think you should change the browser
and if you want
change this
const ModalTimelist = (props: Props) => {
with
const ModalTimelist:React.FC<Props> = (props) => {
Names specified by you in input field attributes must be same as useState names. Otherwise this problem occurs.
Example:
<input type={"text"} className="form-control" placeholder='Enter your User Name' name="username" value={username} onChange={(e)=>onInputChange(e)}/>
In name="username" , username spell must be same as the spell you used in State.

useEffect inside customHook not happens sometimes (from unknown reason). Using useRef

I have this custom hook which supposed to make debounce email validation.
Suddenly I notice that it's not happening in all types.
Sometimes in the first types it's happen sometimes not.
See the log of "useEffect" which won't happen (for me - in each case) with any type. And when it's happening it's taking the previous value.
the Custom hook:
export function useDebounceEmailValidation(value, delay) {
console.log("useDebounceEmailValidation ? ")
// State and setters for debounced value
const [valid, setValid] = useState(true);
const initRun = useRef(true);
console.log("init run = " , initRun.current)
useEffect(
() => {
console.log("useEffect?"); //---------------------------> this not happening on each render
//we don't want to do it on initial running
if(initRun.current){
initRun.current = false;
}
else{
// Update debounced value after delay
const handler = setTimeout(() => {
console.log("validating mail - " ,value);
setValid(validateEmail(value));
// setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
}
},
[value, delay] // Only re-call effect if value or delay changes
);
return valid;
}
the form component:
import React, {useLayoutEffect, useRef, useState} from 'react';
import Button from 'react-bootstrap/Button';
import {useDebounceEmailValidation} from "./utils-hooks";
import {Alert, Col, Form} from "react-bootstrap";
export function SubscribersForm() {
const [details, setDetails] = useState({
firstName: "",
lastName: "",
email: "",
tel: ""
});
const [popupMsg, setPopupMsg] = useState("default");
const [showMsg, setShowMsg] = useState(false);
const [isError, setIsError] = useState(false);
const emailChange = useRef(false);
const [validated, setValidated] = useState(false);
//For cases we need to access the email through local state and not by state
// (the value will persist between component re-rendering and the reference updating won't trigger a component re-rendering)
const emailRef = useRef("");
const validEmail = useDebounceEmailValidation(emailRef.current, 600);
// // general layout effect - will be happen on each component update - for DEBUG
// useLayoutEffect(() => {
// console.log("details = ", details);
// });
//happening after change in the popup message
useLayoutEffect(() => {
setTimeout(() => {
//resetting the msg
setPopupMsg("");
setShowMsg(
false);
}, 2000);
}, [popupMsg])
//happen after change in the details
useLayoutEffect(() => {
//handling email changing (validation)
if (emailChange.current) {
emailRef.current = details.email;
console.log("email.current = " , emailRef.current)
}
}, [details]);
const handleChange = (ev) => {
ev.persist();
if (ev.target.name === "email" ) {
emailChange.current = true;
} else {
emailChange.current = false;
}
setDetails(prevDetails => ({
...prevDetails,
[ev.target.name]: ev.target.value
}));
}
const onSubmit = (ev) => {
const form = ev.currentTarget;
//The default validation for the form
if (form.checkValidity() === false || !validEmail) {
ev.preventDefault();
ev.stopPropagation();
setValidated(true);
return;
}
ev.preventDefault();
alert("Those are the details - you can send it from here to the server !!! :) \n\n" +
"name = " + details.firstName + " " + details.lastName
+ "\nemail = " + details.email
+ "\nphone = " + details.tel);
//we set validation to false, because by default you don't want to show validation
setValidated(false);
setPopupMsg("Your details have been successfully saved");
setIsError(false);
setDetails({
firstName: "",
lastName: "",
email: "",
tel: ""
});
setShowMsg(true);
}
return (
<div className="subscribers-input">
<h3>Subscribers - Form</h3>
<Form className="needs-validation" noValidate validated={validated}
onSubmit={onSubmit}>{/*start of the form block */}
<Form.Row className="">{/*start of the form row of 12/12 columns*/}
<Col xs={12} className="">
<Form.Group controlId="firstName" className="">
<Form.Control
type="text"
placeholder="First name"
value={details.firstName}
onChange={handleChange}
name="firstName"
required
size="sm"
aria-label="first name"
/>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="lastName" className="">
<Form.Control
type="text"
placeholder="Last name"
value={details.lastName}
onChange={handleChange}
name="lastName"
required
size="sm"
aria-label="last name"
/>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="email" className="">
<Form.Control
type="email"
placeholder="Email"
value={details.email}
onChange={handleChange}
name="email"
required
size="sm"
aria-label="email"
isInvalid={!validEmail}
/>
<Form.Control.Feedback type="invalid">Email is Invalid</Form.Control.Feedback>
</Form.Group>
</Col>
</Form.Row>
<Form.Row className="">
<Col xs={12} className="">
<Form.Group controlId="tel" className="">
<Form.Control
type="tel"
placeholder="Phone"
value={details.tel}
onChange={handleChange}
name="tel"
required
size="sm"
aria-label="phone"
/>
</Form.Group>
</Col>
</Form.Row>
{showMsg &&
<Alert variant={isError ? 'danger' : 'success'}>{popupMsg}</Alert>
}
<Button type="submit" size="sm">Save</Button>
</Form>
</div>
)
}
See how the log not always happening.
there are two things to know: first, custom hooks only rerun when the component that we use our custom hook in (in our case, "SubscribersForm"), rerenders. second, the useEffect dependency array checks the equality of objects by references.
so to be sure that useEffect can intercept changes in its dependency array object we should pass new references for that object.
in the main component "SubscribersForm" you pass a reference to your "useDebounceEmailValidation" custom hook, so when you change the value of emailRef.current, no rerenders happen, and also the reference of the object is the same as before. you should use states in these cases

ANTD - How to change input value when blur another input

I'm using ANTD 4.1.2V and I have an input that receives a Zip code number within a form
When onblur event happens, I call a API passing the Zip code value and it returns me the address info.
What I need to do is to fill the others inputs with that values when onBlur event is triggered in Zip code input.
import React, { useState } from 'react';
import { Form, Input, Row, Col } from 'antd';
import MaskedInput from 'antd-mask-input';
import zipCodeAPI from '../../services/zipCodeAPI';
const ClientForm = () => {
const [loadingCep, setLoadingZipCode] = useState(false);
const [, setZipCode] = useState('');
const onChangePfCep = (value) => setZipCode(value);
const handleZipCode = async (cep) => {
setLoadingZipCode(true);
const { data } = await zipCodeAPI(cep);
const { street } = data;
setPublicPlace(street);
setLoadingZipCode(false);
};
return (
<Form
name="client-form"
onFinish={() => console.log('Test')}
layout="vertical"
initialValues={{ ...initialValues, layout: 'vertical' }}
>
<Row gutter={16} justify="space-between">
<Col span={3}>
<Form.Item label="ZIP CODE" name="zip_code">
<MaskedInput
mask="11111-111"
size="8"
onChange={({ target }) => onChangePfCep(target.value)}
// When onBlur here
onBlur={() => handleZipCode('04223-000')}
placeholder="XXXXX-XXX"
/>
</Form.Item>
</Col>
<Col span={7}>
<Form.Item label="Logradouro" name="street">
{/* Fill this input with the zipcodeAPI payload */}
<Input disabled />
</Form.Item>
</Col>
<Col span={2}>
<Form.Item label="Number" name="number">
{/* Fill this input with the zipcodeAPI payload */}
<Input />
</Form.Item>
</Col>
<Col span={5}>
<Form.Item label="Neighborhood" name="neighborhood">
{/* Fill this input with the zipcodeAPI payload */}
<Input disabled />
</Form.Item>
</Col>
</Row>
</Form>
);
};
export default ClientForm;
Does anybody know how to solve this?
You can do it like this
// you need to get the form instance from the useForm hook
const form = useForm();
const handleZipCode = async (cep) => {
setLoadingZipCode(true);
const { data } = await zipCodeAPI(cep);
const { street, number, neighborhood } = data; // changed
setPublicPlace(street);
setLoadingZipCode(false);
form.setFieldsValue({neighborhood, number, street}) // changed
};

State is not passing correctly with redux

Hey i have two form that i want to keep there state seperatly, i added redux and added reducer to keep the state change, but my problem is when i change on form state it's add to the other form state his state also, i want to keep each state seperately
like you see in picture the replace property have been added to the newProjectForm when it only need to be at the roof form.
I have the same reducer on forms because i only need to keep track only onInputChange.
This are my two form:
First Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar,
Toggle } from 'rsuite';
import InputPicker from '../input-picker';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
//onChange = {props.onInputChanged} formValue = {props.newProjectForm}
const options = ["yes","no"];
function NewProjectForm(props){
return (
<Form className='form' layout="horizontal" >
<h1 className='title'>New Project</h1>
<br/>
<FormGroup>
<ControlLabel>Address</ControlLabel>
<FormControl name="address" />
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Affilate</ControlLabel>
<InputPicker name="affilate" data ={options} onChange={props.onInputChanged} style={{width:'300px'}}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Size</ControlLabel>
<FormControl name="size" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bedroom Amount</ControlLabel>
<FormControl name="bedrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Bathrooms amount</ControlLabel>
<FormControl name="bathrooms" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Stories</ControlLabel>
<FormControl name="stories" type="number" />
</FormGroup>
<FormGroup>
<ControlLabel>Has Gas</ControlLabel>
<Toggle name="gas"/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
newProjectForm: state.newProjectForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(NewProjectForm);
Second Form:
import React from 'react'
import {Form, FormGroup, ControlLabel, FormControl, HelpBlock, Button, ButtonToolbar} from 'rsuite';
import './form.css'
import {onInputChanged} from '../../redux/actions/form.actions';
import { connect } from 'react-redux';
import InputPicker from '../input-picker';
function RoofForm(props){
//replace all-roof type description color
const options = ["yes","no"];
// console.log(props);
//onChange={props.onInputChanged}
return (
<Form className='form' layout="horizontal" onChange = {props.onInputChanged} formValue = {props.roofForm}>
<h1 className='title'>Roof</h1>
<br/>
<FormGroup>
<ControlLabel>Replace ?</ControlLabel>
<InputPicker name="replace" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>All Roof?</ControlLabel>
<InputPicker name="all-roof" style={{ width: 300 }} data={options} onChange={props.onInputChanged}/>
<HelpBlock tooltip>Required</HelpBlock>
</FormGroup>
{props.roofForm['all-roof'] === 'yes' && (<div><FormGroup>
<ControlLabel>Type</ControlLabel>
<InputPicker name="type" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Description</ControlLabel>
<InputPicker name="description" style={{ width: 300 }} />
</FormGroup>
{props.roofForm.type === 'shingles' && <FormGroup>
<ControlLabel>Color</ControlLabel>
<InputPicker name="color" style={{ width: 300 }} />
</FormGroup>}
</div>)
}
<FormGroup>
<ControlLabel>Rain diverter</ControlLabel>
<FormControl name="rain-diverter" type="number" style={{ width: 300 }} />
</FormGroup>
<FormGroup>
<ControlLabel>Drip edge</ControlLabel>
<FormControl name="drip-edge" type="number" style={{ width: 300 }}/>
</FormGroup>
<FormGroup>
<ButtonToolbar>
<Button appearance="primary">Save</Button>
<Button appearance="default">Discard</Button>
</ButtonToolbar>
</FormGroup>
</Form>
);
}
const mapStateToProps = (state) => {
return {
roofForm: state.roofForm
}
};
const mapDispatchToProps = (dispatch) => {
return {
onInputChanged: (event) => dispatch(onInputChanged(event))
}
};
export default connect(
mapStateToProps,
mapDispatchToProps)
(RoofForm);
and this is my redux setup:
Form Actions:
export const ON_INPUT_CHANGED = 'ON_INPUT_CHANGED';
export function onInputChanged(event) {
console.log(event);
return(
{
type: ON_INPUT_CHANGED,
payload: event
}
)
}
and this is my reducer:
import {ON_INPUT_CHANGED} from '../actions/form.actions';
function formReducerWrapper(defaultState){
return (state=defaultState, action) => {
switch(action.type){
case ON_INPUT_CHANGED:
state = {
...state,
...action.payload
}
break;
default:
return state;
}
return state;
}
}
const newProjectDefault = {
affilate: {label:"", value: ""}
}
const roofDefault ={
replace:{label:"",value:""},
"all-roof":{label:"",value:""},
type:{label:"",value:""},
description: {label:"",value:""},
color:{label:"",value:""}
}
export const newProjectReducer = formReducerWrapper(newProjectDefault);
export const roofReducer = formReducerWrapper(roofDefault);
Thanks in Advance:)
It looks like you are trying to merge the input change event directly into redux state. That is not how it works.
You can access the HTML input element that changed from the change event using event.target and then read the latest value event.target.value.
If you want the form data to stay separate, then you need to dispatch different information for each form. For example:
dispatch({ type: 'input-changed', form: 'newProject', field: 'affiliate', value: event.target.value });
In your reducers, it should skip any events for a different form.
if (event.form !== 'newProject') return state;
If it becomes tedious there are helper packages like redux-form to help structure the application to avoid repetitive code. It also works fine to store form data in local state instead of putting it into redux.

Adding two input values in one state Reactjs

I want to add a field of trip_start and trip_end in one state called name so that when I send the data, it's combined into one field name.
This is what happens when I submit the data, when I click the first time the state for name shows null, when I click for the second time that's when the state of name is populated with the data of trip_start and trip_end.
Below is my code :
export class DriverPage extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
trip_start:'',
trip_end:'',
nameError:'',
details:'',
detailsError:'',
price: '',
priceError:'',
driver_name: localStorage.getItem('username')
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleSubmit = (event) => {
event.preventDefault();
console.log('state ', this.state.name)
this.setState({name:''+this.state.trip_start+' to ' + this.state.trip_end});
};
handleChange =(evt) => {
this.setState({ [evt.target.name]: evt.target.value });
}
Below is my component which contains the HTML code :
export const DriverComponent = ({handleSubmit, handleChange,obj})=>(
<div className={'bg-image'}>
<Card className={'landing-card'}>
<form onSubmit={handleSubmit}>
<Row>
<Col s={12} m={12} l={12}>
<label s={12} l={12} className={'header'}> Make Ride Request </label>
</Col>
<Input s={12} l={12} type={'text'} value={obj.trip_start} label="Trip Start" name='trip_start' onChange={handleChange}/>
<Input s={12} l={12} type={'text'} value={obj.trip_end} label="Trip End" name='trip_end' onChange={handleChange}/>
<Col s={12} m={12} l={6}>
<div className={'errors'} s={10}>{obj.nameError}</div>
</Col>
<div className="input-field col s12">
<textarea id="details" name='details' onChange={handleChange} className="materialize-textarea"></textarea>
<label htmlFor="details">Trip Details</label>
</div>
<Col s={12} m={12} l={6}>
<div className={'errors'}>{obj.detailsError}</div>
</Col>
<Input s={12} l={12} type="text" value={obj.price} label="Price" name='price' onChange={handleChange} />
<Col s={12} m={12} l={6}>
<div className={'errors'}>{obj.priceError}</div>
</Col>
<Col s={12} m={12} l={12}>
<Button waves='light' className={`purple button-align`} value='submit' type='submit' >Create Ride Request</Button>
</Col>
</Row>
</form>
</Card>
</div>
);
So in summary, I want the state trip_start and trip_end to be concatenated on the state name instantly while am going to send the data.
For example this is a simple pseudo code for what I want to achieve:
trip_start = new york
trip_end = toronto
name = trip_start + "to " +trip_end
Output
name = new york to toronto
Is there any reason why it has to be concatenated before submission? Seems like you're making your life harder by combining two asynchronous strings synchronously. Instead, just make your life easier and concatenate upon form submission.
Concatenated on form submission example (best solution): https://codesandbox.io/s/10w18m5z54
containers/Form.js
import React, { Component } from "react";
import Fields from "../components/Fields";
export default class Form extends Component {
state = {
tripStart: "",
tripEnd: ""
};
handleChange = ({ target: { value, name } }) =>
this.setState({
[name]: value
});
handleSubmit = e => {
e.preventDefault();
const { tripEnd, tripStart } = this.state;
const name = `${tripStart} to ${tripEnd}`;
alert(name);
};
render = () => (
<Fields
{...this.state}
handleSubmit={this.handleSubmit}
onHandleChange={this.handleChange}
/>
);
}
Concatenated on input change example (works, but not an ideal solution as it causes double rerendering -- also, this.state.name is never used in your component, so utilizing state is unnecessary and not recommended!): https://codesandbox.io/s/nk66v4rk9j
containers/Form.js
import React, { Component } from "react";
import Fields from "../components/Fields";
export default class Form extends Component {
state = {
tripStart: "",
tripEnd: ""
};
handleChange = ({ target: { value, name } }) =>
this.setState(
{
[name]: value
},
() =>
this.setState({
name: `${this.state.tripStart} to ${this.state.tripEnd}`
})
);
handleSubmit = e => {
e.preventDefault();
const { name } = this.state;
alert(name);
};
render = () => (
<Fields
{...this.state}
handleSubmit={this.handleSubmit}
onHandleChange={this.handleChange}
/>
);
}

Resources