Hi guys i have this problem. I have input and dropdown I need choose from option dropdown and write value on input but first value was everytime empty. I solved this problem with componentDidUpdate and set locationValue on undefined. After i have sometimes this error.How can I fixed it? I need change lifecycle method or what ?
Here is code
class AccordionForm extends Component {
state = {
value: '',
locationValue: undefined,
};
componentDidUpdate() {
const { nameOptions } = this.props;
if (nameOptions && nameOptions && this.state.locationValue === undefined) {
this.setState({
locationValue: nameOptions[0],
});
}
}
handleChangeInputSelect = ({ target }) => {
this.setState({
locationValue: target.value,
});
};
handleChangeInput = ({ target }) =>
this.setState({
value: target.value,
});
onSubmit = event => {
const { value, locationValue } = this.state;
const { handleSubmitForm } = this.props;
event.preventDefault();
handleSubmitForm(value, locationValue);
this.setState({ value: '' });
};
render() {
const { name, nameOptions } = this.props;
const { value } = this.state;
return (
<Form className="accordion_form" name={name} onSubmit={this.onSubmit}>
<FormGroup className="form-group-locations">
{nameOptions && (
<Input
className="form-input"
required
name={name}
type="select"
onChange={this.handleChangeInputSelect}
>
{nameOptions.map((option, index) => {
return (
<option key={index} value={option}>
{option}
</option>
);
})}
</Input>
)}
<Input
placeholder={`add new ${name}...`}
type="text"
required
name={name}
value={value}
onChange={this.handleChangeInput}
className="form-input"
/>
<Button className="tagBtn" color="success" type="submit">
Add
</Button>
</FormGroup>
</Form>
);
}
}
export default AccordionForm;
This could be happening because at the first instance your nameOptions[0] is undefined. Please update your if statement's conditions like this:
componentDidUpdate() {
const { nameOptions } = this.props;
if (nameOptions && !!nameOptions.length && nameOptions[0] && this.state.locationValue !== nameOptions[0]) {
this.setState({
locationValue: nameOptions[0],
});
}
}
Related
I have a form component where I create the template of fields that can be initiated in another component, then in the parent component's template, I define fields properties. Problem is that my form submit button is in the parent component which means I have no access to input values from here.
This is Parent component:
function SignIn() {
//Here I will be doing API call and will need to use input values
};
function Login() {
let template = {
fields: [
{
title: data.email,
type: 'email',
name: data.email,
placeholder: data.inputPlaceholder,
icon: letter,
},
{
title: data.password,
type: 'password',
name: data.password,
placeholder: data.inputPlaceholder,
icon: lock,
}
]
}
return (
<Form template={template} />
<Button btnstyle={'mazhrBtn light-green'} onClick={SignIn}>
{data.signInButton}
</Button>
);
}
This is Form component:
function Form({ template, children, onSubmit, errors }) {
const [ value , setValue ] = useState('');
let [ formErrors, setFormErrors] = useState(false);
let { fields } = template;
const handleSubmit = (event) => {
event.preventDefault();
onSubmit({
text: value
});
if(errors) {
formErrors = true;
setFormErrors(formErrors);
} else {
formErrors = false;
setFormErrors(formErrors);
event.target.reset();
document.querySelector('.editable').innerHTML = "";
}
};
const renderFields = (fields) => {
return fields.map((field, index) => {
let { title, type, name, placeholder, icon, textArea, dropdown, dropdownTemplate } = field;
const onChange = (event) => {
let eName = event.target.name;
let eValue = event.target.value;
setValue({
...value,
[eName]: eValue
});
console.log(eName + ':' + eValue);
if(icon) {
if (event.target.value !== '') {
document.getElementById('icon' + index).classList.add('active');
} else {
document.getElementById('icon' + index).classList.remove('active');
}
}
};
return (
<div key={index} className="form-field">
<label htmlFor={name}>{title}</label>
<div className="input-wrapper">
{dropdown ?
<DropdownButton dropdownTemplate={dropdownTemplate} placeholder={placeholder} customClass='input'/>
:
textArea ?
<p className="m-0 editable"
contentEditable
type={type}
name = {name}
id={name}
placeholder={placeholder}
value={index.value}
onChange={onChange}
suppressContentEditableWarning={true}
onInput={
e => setValue(
{
...value,
[e.target.getAttribute("name")]: e.currentTarget.textContent
}
)
}
></p>
:
<input
type={type}
name={name}
id={name}
placeholder={placeholder}
value={index.value}
onChange={onChange}
/>
}
{icon ? <img id={'icon' + index} className="icon" src={icon} alt="icon"/> : ''}
</div>
</div>
)
})
}
return (
<>
<form className="form" onSubmit={handleSubmit}>
{ renderFields(fields) }
{children}
</form>
</>
)
}
Sorry in advance for a messy code, I'm learning React
In general, when you need the state in a parent component, the best thing to do is to lift the state up. See https://reactjs.org/docs/lifting-state-up.html
That means moving your state hooks to the parent component and passing them down as props to the child component
I have passed my user ID into my 'OrderMessages' component but in my function says undefined. When my user submits a messages using the form in the handleFormSubmit function I need the UserID and the datetime of the message. I have managed to get the date and time but when trying to console log to get the UserID I keep getting an error. I have tried this.props.... and this.state but both say undefined, can you please help. In my constructor I have tested using const UserId = props.location.state.UserID; and in debug I can see this has correctly got the UserID so im not sure how to get it into my hadleFormSubmit function.
import React from "react";
import Moment from "moment";
import { Form, Button } from "react-bootstrap";
class OrderMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
checkboxes: [],
selectedId: [],
formLableSelected: "",
formSelectedSubject: "",
formSelectedSubjectId: "",
formNewSubject: "",
formChainID: "",
formMessageBody: "",
userId: '',
};
const UserId = props.location.state.UserID;
}
componentDidMount() {
this.setState({ isLoading: true });
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url =
"myURL" +
this.props.location.state.orderNumber;
fetch(proxyurl + url)
.then((res) => res.json())
.then((data) => this.setState({ data: data, isLoading: false }));
}
handleClick = (id) => {
if (this.state.selectedId !== id) {
this.setState({ selectedId: id });
} else {
this.setState({ selectedId: null });
}
};
setformSubjectID(messageSubject) {
if (messageSubject.subject === this.state.formSelectedSubject) {
this.setState({ formSelectedSubjectId: messageSubject.messageSubjectId });
}
}
handleChangeSubject = (event) => {
this.setState({ formSelectedSubject: event.target.value });
this.state.data.message_Subjects.map((ms) => this.setformSubjectID(ms));
};
handleFormSubmit(e) {
e.preventDefault();
// get current time
let submit_time = Moment().format("ddd DD MMM YYYY HH:mm:ss");
console.log("messageDatetime", submit_time);
// get user id THIS IS WHAT DOESN’T WORK
console.log("messageSentFrom", this.state.userId);
console.log("messageSentFrom", this.props.location.state.UserID);
}
render() {
const { data, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
if (data.length === 0) {
return <p> no data found</p>;
}
console.log("mess: ", data);
return (
<div>
<div className="sendMessageContent">
<Form className="sendMessageForm" onSubmit={this.handleFormSubmit}>
<Form.Group className="formRadio">
<Form.Check
className="formRadiob"
type="radio"
label="New chat"
value="new"
name="neworexisitng"
id="New Message"
onChange={this.onFormMessageChanged}
defaultChecked
/>
<Form.Check
className="formRadiob"
type="radio"
label="Reply to exisiting chat"
value="reply"
name="neworexisitng"
id="exisiting Message"
onChange={this.onFormMessageChanged}
/>
</Form.Group>
{this.returnCorrectFormFields(data)}
<Form.Group>
<Form.Label>Message Body</Form.Label>
<Form.Control as="textarea" rows={3} />
</Form.Group>
<Button variant="primary" type="submit">
Send Message
</Button>
</Form>
</div>
</div>
);
}
returnCorrectFormFields(data) {
if (this.state.formLableSelected === "new") {
return this.newMessageSubject(data);
} else {
return this.choseMessageSubject(data);
}
}
choseMessageSubject(data) {
return (
<Form.Group>
<Form.Label>Select the message subject</Form.Label>
<Form.Control as="select" onChange={this.handleChangeSubject}>
<option value="0">Choose...</option>
{data.message_Subjects.map((ms) => (
<option value={ms.subject}>{ms.subject}</option>
))}
</Form.Control>
</Form.Group>
);
}
newMessageSubject(data) {
return (
<Form.Group>
<Form.Label>Enter Message Subject</Form.Label>
<Form.Control type="text" placeholder="Enter message subject" />
</Form.Group>
);
}
onFormMessageChanged = (event) => {
this.setState({
formLableSelected: event.target.value,
});
};
getAllMessageInChain(messageChain) {
return (
<div className="messageHistory">
<div className="messageHistoryHeader">
<div className="innerMS-history-body">Message</div>
<div className="innerMS">Date and Time</div>
<div className="innerMS">Message sent by</div>
</div>
{messageChain.map((ms) => (
<div className="messageHistoryBody">
<div className="innerMS-history-body">{ms.messageBody}</div>
<div className="innerMS">
{Moment(ms.dateTime).format("ddd DD MMM YYYY hh:mm:ss")}
</div>
<div className="innerMS">{ms.sentFromId}</div>
</div>
))}
</div>
);
}
getLatestMessageDateTime(messageChain) {
const lastmessage = messageChain.length - 1;
Moment.locale("en");
var dt = messageChain[lastmessage].dateTime;
return Moment(dt).format("ddd DD MMM YYYY hh:mm:ss");
}
}
export default OrderMessages;
The scope of this isn't the component in the function you're using.
Either change handleFormSubmit to this to bind this automatically.
handleFormSubmit = (e) => {
// .. your code
}
or bind this manually in the constructor
constructor() {
// ..other code
this.handleFormSubmit = this.handleFormSubmit.bind(this)
}
I'm trying to make a multi-step form using React.js and material UI. for validation purpose I am using Joi-Browser. But I am getting error from Joi while validation, stating that error: ValidationError: "value" must be an object
I am very new to React.js Please guide me what I am doing wrong here.
here what I have tried so far.
class ServiceRequestForm extends Component {
state = {
step: 1,
service_category: [],
user : [
{
full_name: '',
address_one: '',
}
],
hasError: false
}
schema = Joi.object().keys({
full_name: Joi.string().alphanum().min(3).max(100).required(),
address_one: Joi.string().required(),
});
validate = () => {
const result = Joi.validate(this.state.user, this.schema)
console.log(result)
}
// Proceed to next step
nextStep = () => {
const { step } = this.state;
this.setState({
step: step + 1
});
}
// Proceed to prev step
prevStep = () => {
const { step } = this.state;
this.setState({
step: step - 1
});
}
// handle select
handleChange = (event)=> {
this.setState(oldValues => ({
...oldValues,
[event.target.name]: event.target.value,
}));
}
// handle input
handleChangeInput = name => event => {
this.setState({ [name]: event.target.value });
};
handleSubmit = ()=>{
this.validate();
}
render() {
const { step } = this.state;
const { service_category } = this.state;
const { full_name, address_one } = this.state.user;
const values = { service_category, full_name, address_one };
switch (step) {
case 1:
return (
<CategoryForm
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
);
case 2:
return (
<AddressForm
prevStep={this.prevStep}
handleChangeInput={this.handleChangeInput}
handleSubmit={this.handleSubmit}
values={values}
/>
);
case 3:
return (
<ThankYouPage
/>
);
}
}
}
export default ServiceRequestForm;
// Category form
export default class CategoryForm extends Component {
continue = e => {
e.preventDefault();
this.setState({ hasError: false });
if (!this.props.values.service_category) {
console.log(this.props.hasError);
this.setState({ hasError: true });
}
else {
this.props.nextStep();
}
}
render() {
const { handleChange, values, classes, nextStep, hasError } = this.props;
return (
<div>
<h4>Select service you want</h4>
<form>
<FormControl error={hasError}>
<Select
value={values.service_category}
onChange={handleChange}
inputProps={{
name: 'service_category'
}}
>
<MenuItem value="">
<em>Select Category</em>
</MenuItem>
<MenuItem value={10}>House Maid</MenuItem>
<MenuItem value={20}>Electricians</MenuItem>
<MenuItem value={30}>Plumber</MenuItem>
</Select>
<FormHelperText>Please select service category</FormHelperText>
{hasError && <FormHelperText>This is required!</FormHelperText>}
</FormControl>
</form>
<br />
<Button variant="contained" color="primary" onClick={this.continue}>Next</Button>
</div>
)
}
}
// address form
export default class AddressForm extends Component {
back = e => {
e.preventDefault();
this.props.prevStep();
}
render() {
const { handleChangeInput, values, classes, handleSubmit, prevStep, hasError, full_name } = this.props;
return (
<div>
<h1>Address</h1>
<TextField
label="Full Name"
//className={classes.textField}
value={values.full_name}
onChange={handleChangeInput('full_name')}
margin="normal"
variant="outlined"
/>
<TextField
label="Address Line 1"
//className={classes.textField}
value={values.address_one}
onChange={handleChangeInput('address_one')}
margin="normal"
variant="outlined"
/>
<Button variant="contained" color="primary" onClick={handleSubmit}>Submit</Button>
<Button variant="contained" color="primary" onClick={prevStep}>Back</Button>
</div>
);
}
}
Schema can be just an object.
Try using like below.
{
full_name: Joi.string().alphanum().min(3).max(100).required(),
address_one: Joi.string().required(),
}
No need to specify
Joi.object().keys(
Trying to display a dynamic Form based on Formik in Reactjs but when i try to submit there's no error and nothing is submitted.
Although i do get a warning sayning:
A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
this is my component DynamicForm that has all the behaviors of the form.
class DynamicForm1 extends Component {
renderCheckBox(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<Field
name={input.name}
render={(prop) => {
const { field } = prop;
return (
<input
name={input.name}
type="checkbox"
checked={field.value}
onChange={field.onChange} />
);
}}
/>
</Fragment>
);
}
renderTextArea(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
return (
<div>
<textarea {...field} id={hasError}>
</textarea>
</div>
);
}}
/>
</div>
</Fragment>
);
}
renderSelect(input) {
return (
<Fragment key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
const defaultOption = <option key='default' value='Please Select'>Please Select</option>;
const options = input.data.map(i => <option key={i} value={i}> {i} </option> );
const selectOptions = [defaultOption, ...options];
return (
<div className='dropdown'>
<select value={field.value} {...field} id={hasError}>
{
selectOptions
}
</select>
</div>
);
}}
/>
</div>
</Fragment>
);
}
renderFields(inputs) {
return inputs.map(input => {
if(input.type === 'select') {
return this.renderSelect(input);
}
if(input.type === 'checkbox') {
return this.renderCheckBox(input);
}
if(input.type === 'textarea') {
return this.renderTextArea(input);
}
return (
<div key={input.name}>
<label>{input.label}</label>
<div>
<Field
name={input.name}
render={(props) => {
const { field } = props;
const { errors, touched } = props.form;
const hasError = errors[input.name] && touched[input.name] ? 'hasError' : '';
return (
<input
{...field}
id={hasError}
type='text'
/>
);
}}
/>
</div>
</div>
);
})
}
getInitialValues(inputs) {
//declare an empty initialValues object
const initialValues = {};
//loop loop over fields array
//if prop does not exit in the initialValues object,
// pluck off the name and value props and add it to the initialValues object;
inputs.forEach(field => {
if(!initialValues[field.name]) {
initialValues[field.name] = field.value;
}
});
//return initialValues object
console.log(" initial values1" + initialValues);
return initialValues;
}
render() {
const initialValues = this.getInitialValues(this.props.fields);
console.log(" initial values2" + JSON.stringify(initialValues));
return (
<div className="app">
<h1>Dynamic Form</h1>
<Formik
onSubmit={(values) => {console.log("values :" +JSON.stringify(values))}}
validationSchema={this.props.validation}
initialValues={initialValues}
render={(form) => {
const errorMessageShow = Object.keys(form.errors).length > 0 ? 'error' : 'hidden';
return <div>
<form onSubmit={form.handleSubmit} onChange={form.handleChange}>
<div className={errorMessageShow}>
Please correct the errors below
</div>
{this.renderFields(this.props.fields)}
<button type='submit' className='btn'>Submit</button>
</form>
</div>
}}
/>
</div>
);
}
}
export default DynamicForm1;
And then there is App.js that contains my data that i collect from an API and i push it to the DynamicForm.
class App3 extends Component {
constructor(props){
super(props);
this.state={
fields:[]
};
this.getData = this.getData.bind(this);
}
getData(){
axios.get(`http://localhost:3006/formulaire`)
.then(res => {
this.setState({
fields: res.data
})
})
}
componentDidMount() {
this.getData();
}
render() {
return (
<DynamicForm1 fields={this.state.fields} validation={validation} />
)
}
}
export default App3;
I'm creating an input element using document.createElement and setting the attribute. i would like to have an onchange event which will update the state.
how can i proceed? following is my code
addoffers = () => {
var input = document.createElement("input");
input.setAttribute('name', 'whatweoffer'.concat(Math.floor((Math.random() * 1000) + 1)));
input.setAttribute('class','form-control mb-3');
input.setAttribute('placeholder','what we offer');
input.onchange = this.handleChange;
var parent = document.getElementById("offerinput");
parent.appendChild(input);
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(e.target.value);
}
<input type="text" className="form-control mb-3" id='whatweoffer' name='whatweoffer' onChange={this.handleChange} placeholder='what we offer' required />
Try this
class DynamicInputs {
constructor(props) {
super(props);
this.state = {
inputs : []
}
}
render() {
return (
<React.Fragment>
{
this.state.inputs.map(inputValue =>{
<input key= {inputValue} type="text" placeholder="what we offer" className="form-control mb-3" name = {`whatweoffer${inputValue}`} onChange = {this.handleChange} required/> })
}
</React.Fragment>
)
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(e.target.value);
}
addInput() = () =>{
let {inputs} = this.state;
inputs.push((Math.floor((Math.random() * 1000) + 1))
this.setState({
inputs
})
}
}
Although there's accepted answer but I want to give you another approach.
Instead of create input dynamically, why don't you just use own component's state to decide if an input is displayed or not, and other attributes are just state values. For example:
render() {
const {
isInputDisplayed, inputName, inputId, inputClasses, inputPlaceholder,
} = this.state;
return (
{ isInputDisplayed && (
<input type="text" name={inputName} id={inputId} className={inputClasses} placeholder={inputPlaceholder} />
)}
);
}