How am I switching between an uncontrolled/controlled input? React - reactjs

I have a component that has an input:
state = {
org: {
orgName: ''
}
};
updateInput = field => event => {
this.setState({
org: {
[field]: event.target.value
}
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={this.updateInput('orgName')}
/>
)
}
I initialize the input value to '', but as soon as I type anything into the input, I get this error:
A component is changing a controlled input of type undefined to be uncontrolled
I thought if I initialized the input, then it would always be controlled, but apparently this is wrong. What is the proper way for this input to always be controlled?

Use the following format:
constructor(){
super()
this.state = {
org: {
orgName: ''
}
};
}
updateInput = (field, event) => {
this.setState({
org: {
[field]: event.target.value
}
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={(event) => {this.updateInput('orgName', event)}}
/>
)
}
If you use it like this then you get:
place state inside a constructor so you can change it with this.setState.
onChange is being fired only when an event is happening and not automatically fired.
You need to pass the event down through the handler chain so the handler as access to it.
not sure what you meant by updateInput = field => event => {} there is no event in the chain at that point so you cannot access it.
Hope it helps :)

Related

How to update state inside componentDidMount?

I'm using fetch API and I want update the const called state inside the componentDidMount() (with onChange) which are being using in a template string. How do I update this value with onChange?
import React, {Component} from 'react'
class Data extends Component {
constructor() {
super();
this.state = {
items: {},
value: '',
isLoaded: false
}
}
handleChange(e) {
this.setState({value: e.target.value});
}
componentDidMount() {
const state = this.state.value
fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${state}`)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json,
})
});
}
render(){
const {isLoaded} = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
}
So, how can I update the value of the const state with onChange?
componentDidMount() is called when the React component has mounted, and it happens only once.
If I understand correctly, you want to call fetch on each change of the value stored under value state property, so the componentDidMount method is not a perfect place to put that kind of logic. You can create a separate method called fetchData and pass the value to it as an argument. Then you can call that method on componentDidMount as well as on each value property change (in our case - onChange event).
import React, { Component } from "react";
class Data extends Component {
constructor(props) {
super(props);
this.state = {
items: {},
value: "America/Chicago",
isLoaded: false
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
const { value } = this.state;
this.fetchData(value);
}
handleChange(event) {
const value = event.target.value;
this.setState({
value
});
this.fetchData(value);
}
render() {
const { isLoaded, value, items } = this.state;
if (!isLoaded) {
return <div>Loading...</div>;
}
return (
<div>
<select onChange={this.handleChange} value={value}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
{JSON.stringify(items)}
</div>
);
}
fetchData(value) {
fetch(
`https://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}`
)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json
});
});
}
}
Working demo: https://codesandbox.io/embed/728jnjprmq
Assuming you want to refresh the value of this.state.items when the user changes the value of the select, you can do this in the onChange. However, your code is in a few (incorrect) pieces. Let's start from the top.
First of all, you're setting the value property of state to '', so your componentDidMount function is going to see that value. I assume that's no good, so let's strip that out of componentDidMount entirely. We can move this code to the handleChange function instead, but it'll still need to be changed:
handleChange(e) {
this.setState({value: e.target.value});
fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${e.target.value}`)
.then(res => res.json())
.then(json => {
this.setState({
isLoaded: true,
items: json,
})
});
}
Notice my change - we can't access the value from the state, because setState is asynchronous, and so the value hasn't been updated by this point. We know the value comes from the select though.
The other thing you could do to improve this functionality is to turn the select into a controlled component. To do this, you just have to set the value of the field to be controlled by the state of this component. Since you're using an onChange listener for this, it makes the field a controlled component (if you weren't using an onChange, it would be a read-only field.
The loading variable in state appears to be being used incorrectly, I'm guessing you just need to check if there's data in 'items'. I'll remove this for now, but you could come back to this.
render(){
const {isLoaded} = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
Tomasz's code has 2 mistakes: (1) it fetches resources w/o checking if the component has been unmounted; (2) it starts the request w/o updating the UI first.
I would do the following instead:
import React, {Component} from 'react'
class Data extends Component {
constructor() {
super();
this.state = {
items: {},
value: '',
isLoaded: false
}
this._isMounted = false;
// don't forget to bind your methods
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
handleChange(e) {
const value = e.target.value;
this.setState({ value }, () => {
if (!this._isMounted) return;
const url = `http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}`
fetch(url).then((res) => {
if (!this._isMounted) return;
const data = res.json();
this.setState({ isLoaded: true, items: data });
})
});
}
render(){
const { isLoaded } = this.state;
if(!isLoaded) {
return <div>Loading...</div>
}
return(
<div>
<select onChange={this.handleChange}>
<option value="America/Chicago">Chicago</option>
<option value="America/Sao_Paulo">São Paulo</option>
</select>
</div>
)
}
}

Controlled Inputs, displayed values will not update to last digit

i'm using React(Typescript Version) to display some input inside a form.
The problem (as you can see from the image) is that when i update the values, from the setState function, values will not 'scroll' on the right
render() {
return(
<input
name={this.props.input.Name}
type={this.props.input.Type}
defaultValue={this.state.value}
ref={this._input}
key={key()}
)}
The function that updates the Value is a common set Function :
public set Value(data: string) {
this.setState({
internalValue: data,
inputError: !this.validateValue(data)
});
}
Note that the input works as expected if i write from the Keyboard, but if i write the input using a 'simulated' keyboard on screen happens what i just described
Any ideas?
Thank you
Update after simbathesailor support:
render() {
return(
<input
name={this.props.input.Name}
type={this.props.input.Type}
defaultValue={this.state.value}
ref={this._input}
key={key()}
onChange={this.setValue}
/>
)
}
componentDidUpdate(prevProps: InputProps, prevState: InputState) {
if (prevState.value!== this.state.value) {
this._input.current.focus();
}
}
setValue(event: React.ChangeEvent<HTMLInputElement>) {
console.log('change');
this.setState({
value: event.target.value
})
}
shouldComponentUpdate(nextProps: InputProps, nextState: InputState): boolean {
return (this.state.value!= nextState.value);
}
public set Value(data: string) {
this.setState({
value: data,
inputError: !this.validateValue(data)
}, () => {
this._input.current.focus();
});
}
You can use the refs and commit lifecycle method componentDidUpdate method. to achieve this.
In the example mentioned below, it is done for the uncontrolled component. But idea will remain same for controlled component also.
class Test extends React.Component {
constructor(props) {
super(props)
this.InputRef = React.createRef()
this.state = {
value: 0
}
}
setValue = (event) => {
this.setState({
value:event.target.value
})
}
update = () => {
this.setState({
value: (this.state.value || 0) + 1000
})
}
componentDidUpdate(prevProps, prevState) {
if(prevState.value !== this.state.value) {
this.InputRef.current.focus()
}
}
render() {
return (
<div>
<input
value={this.state.value}
onChange={this.setValue}
ref={this.InputRef}
/>
<button onClick={this.update}>update</button>
</div>
)
}
}
ReactDOM.render(<Test />, document.getElementById("root"))
Here is the codepen link to see it working:
Uncontrolled approach(javascript) codepen link
Controlled approach(javascript) codepen link
I have tried typescript for the first time. Thanks for your question :). Typescript is good. And here is your desired solution needed in typescript.
Codesandbox link(Typescript)

Testing local state using `evt.target.name` doesn't work

When running enzyme specs, I have this spec that I can't seem to pass using evt.target.name in my event handler. The spec looks like this:
describe('<CampusInput /> component', () => {
let renderedCampusInput
let campusInputInstance
beforeEach(() => {
renderedCampusInput = shallow(<CampusInput />)
campusInputInstance = renderedCampusInput.instance()
})
it.only('handleChange should update the local state', () => {
renderedCampusInput.find('input').simulate('change', {
target: { value: 'Another Campus Name' }
})
expect(campusInputInstance.state.name).to.equal('Another Campus Name')
})
})
My component looks like this:
export default class CampusInput extends Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(evt) {
// this line doesn't pass the spec :(
this.setState({ [evt.target.name]: evt.target.value })
// this passes the spec
this.setState({ name: evt.target.value })
}
render() {
return (
<div>
<input
name="name"
type="text"
onChange={this.handleChange}
value={this.state.name}
/>
</div>
)
}
}
I can only get it to pass if I hard-code the key in my setState(). How can I re-write the specs so that it passes if I use setState({[evt.target.name]: evt.target.vale})?
First of all you are expecting name of the input to equal it's value , the second thing is you didn't add the name key to the event object .
In order to solve this we need to add the name key to the event in your test:
renderedCampusInput.find('input').simulate('change', {
target: { name:'name',value: 'Another Campus Name' }
})
and change the expect method to equal 'name'
expect(campusInputInstance.state.name).to.equal('name')
also you can add expect value of the input
expect(campusInputInstance.state.value).to.equal('Another Campus Name')
but don't forget to add it to the component state
this.state = {
name: '',
value:'',
}

How to check form is valid or not in react + material?

Is there any way to know that form is valid or not in react + material ui .I am using react material in my demo .I have three field in my form all are required . I want to check on submit button that form is valid or not
Here is my code
https://codesandbox.io/s/w7w68vpjj7
I don't want to use any plugin
submitButtonHandler = () => {
console.log("error");
console.log(this.state.form);
};
render() {
const { classes } = this.props,
{ form } = this.state;
return (
<div className={classes.searchUser__block}>
<SearchForm
handleInput={this.handleInputFieldChange}
submitClick={this.submitButtonHandler}
form={form}
/>
</div>
);
}
You would have to manually do that verification if you don't want to use any library. Material-ui does not have any validation built in as per their documentation. BUT it does give you some tools for that like errorMessage to text fields for example. You just have to play with it
Example:
class PhoneField extends Component
constructor(props) {
super(props)
this.state = { errorText: '', value: props.value }
}
onChange(event) {
if (event.target.value.match(phoneRegex)) {
this.setState({ errorText: '' })
} else {
this.setState({ errorText: 'Invalid format: ###-###-####' })
}
}
render() {
return (
<TextField hintText="Phone"
floatingLabelText="Phone"
name="phone"
errorText= {this.state.errorText}
onChange={this.onChange.bind(this)}
/>
)
}
}
a bit outdated example i had laying around
Form validation can be pretty complex, so I'm pretty sure you'll end up using a library. As for now, to answer your question, we need to think about form submission flow. Here is a simple example:
"Pre-submit"
Set isSubmitting to true
Proceed to "Validation"
"Validation"
Run all field-level validations using validationRules
Are there any errors?
Yes: Abort submission. Set errors, set isSubmitting to false
No: Proceed to "Submission"
"Submission"
Proceed with running your submission handler (i.e.onSubmit or handleSubmit)
Set isSubmitting to false
And some minimal implementation would be something like:
// ...imports
import validateForm from "../helpers/validateForm";
import styles from "./styles";
import validationRules from "./validationRules";
const propTypes = {
onSubmit: PropTypes.func.isRequired,
onSubmitError: PropTypes.func.isRequired,
initialValues: PropTypes.shape({
searchValue: PropTypes.string,
circle: PropTypes.string,
searchCriteria: PropTypes.string
})
};
const defaultProps = {
initialValues: {}
};
class SearchForm extends Component {
constructor(props) {
super(props);
this.validateForm = validateForm.bind(this);
this.state = {
isSubmitting: false,
values: {
searchValue: props.initialValues.searchValue || "",
circle: props.initialValues.circle || "",
searchCriteria: props.initialValues.searchCriteria || ""
},
...this.initialErrorState
};
}
get hasErrors() {
return !!(
this.state.searchValueError ||
this.state.circleError ||
this.state.searchCriteriaError
);
}
get initialErrorState() {
return {
searchValueError: null,
circleError: null,
searchCriteriaError: null
};
}
handleBeforeSubmit = () => {
this.validate(this.onValidationSuccess);
};
validate = (onSuccess = () => {}) => {
this.clearErrors();
this.validateForm(validationRules)
.then(onSuccess)
.catch(this.onValidationError);
};
onValidationSuccess = () => {
this.setState({ isSubmitting: true });
this.props
.onSubmit(this.state.values)
.catch(this.props.onSubmitError)
.finally(() => this.setState({ isSubmitting: false }));
};
onValidationError = errors => {
this.setState({ ...errors });
};
clearErrors = () => {
this.setState({ ...this.initialErrorState });
};
updateFormValue = fieldName => event => {
this.setState(
{
values: { ...this.state.values, [fieldName]: event.target.value }
},
() => this.validate()
);
};
render() {
// ...
}
}
SearchForm.propTypes = propTypes;
SearchForm.defaultProps = defaultProps;
export default withStyles(styles)(SearchForm);
As you can see, if submission flow will grow larger (for example touching inputs, passing errors, etc), the of amount of complexity inside of a component will significantly grow as well. That is why it's more preferable to use a well-maintained library of choice. Formik is my personal preference at the moment.
Feel free to check out updated codesandbox. Hope it helps.
Hi Joy I've made desirable form validation if required fields are empty.
Here is the updated codesandbox: https://codesandbox.io/s/50kpk7ovz4

React: Updating one state property removes other states properties in the state

I have two text boxes with id set dynamically as en which can change. Every time the onchange is emittted from input field, the state should update. The problem is, if I type something into one of the inputs, the other input state seems to disappear. For example, If I type test into title text field, the state becomes:
this.state = {
translation: {
en: {
title: 'test'
}
}
};
If I move on to typing into the content text box, it seems to replace the title state. like so,
this.state = {
translation: {
en: {
content: 'content'
}
}
};
They should update the state independently without affecting each other. Here is my intended state
this.state = {
translation: {
en: {
title: 'title-text',
content: 'content-text'
}
}
};
Component
import React from 'react';
export default class Demo extends React.Component
{
constructor(props)
{
super(props);
// default state
this.state = {
translation: {}
};
}
onSubmit(e)
{
e.preventDefault();
console.log(this.state);
}
render()
{
return (
<form onSubmit={(event) => this.onSubmit(event)}>
<input id="en" name="title"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
title: event.target.value
}
}
})} />
<input id="en" name="content"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
content: event.target.value
}
}
})} />
<button type="submit">Login</button>
</form>
);
}
}
setState does not deeply merge current state and updates. You should spread your translation state prop.
this.setState({
translation: {
...this.state.translation,
[event.target.id]: {
content: event.target.value
}
}
})
The behaviour is correct.
e.g: var obj = {a:1,b:3};
obj = {a:4};//Obj updated. Now obj.b will be undefined.This is what you worried for.
In your case, you can do something like this. It is not one of the best solution.
onSubmit(e)
{
e.preventDefault();
let state = this.state.translation;
state.en.content = 'content';
this.setState(state);
console.log(this.state);
}
As was mentioned, setState doesn't deeply merge values, however what you really need is to do is this:
this.setState({
translation: {
[event.target.id]: {
...this.state.translation[event.target.id],
content: event.target.value
}
})
And same for title.
You forgot about immutability. Just add to your code ...this.state for import all the properties that have been there before.
this.state = {
...this.state,
..
}

Resources