How to change the "error" prop of a material-ui element? - javascript

I am trying to validate a form field and have the following code:
import React, {Component} from 'react';
import {TextField} from '#material-ui/core';
class ProductField extends Component {
constructor(props) {
super(props);
this.isValid = this.isValid.bind(this);
}
isValid() {
console.log("Checking if valid...");
}
componentDidMount() {
console.log("this isn't working");
}
render() {
return (
<TextField style={{width:"100%"}} id={this.props.label} label={this.props.label} variant="outlined"
hintText={this.props.label}
helperText={this.props.meta.touched && this.props.meta.error}
onChange={event => {
console.log("changed");
const { value } = event.target;
this.setState({ searchValue: value });
}}
{...this.props.input}
/>
);
}
}
export default ProductField;
When onChange is called, I want to check the state of the TextField, and if this.props.meta.error is not empty I want to set the Text Field prop "error" and if it is empty then I want to unset the "error" prop.
Right now, even the console.log("Checking if valid...") isn't working, suggesting that the onChange event isn't being fired at all.
What am I doing wrong?

You have several issues in your code:
You need to initialize your state in the constructor, so the state will be reliable with you Textfield, also you have to set a property in the state that will handle if the field is valid or is an `error'.
You need to create a method to validate your TextField value, and inside this method update the state based on the validity of field, here you can add the this.props.meta.error or anything else that is validating your values, and remember to call the method that will validate your value after the state has been properly changed, maybe using it as a callback on the setStatemethod .
You have to add a prop to your TextField component to catch if it is an error or if it is valid.
import React, { Component } from "react";
import { TextField } from "#material-ui/core";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchValue: "",
isValid: false
};
this.isValid = this.isValid.bind(this);
}
isValid() {
console.log("Checking if valid...");
this.setState({ isValid: this.state.searchValue.length > 6 });
}
componentDidMount() {
console.log("this isn't working");
}
render() {
return (
<TextField
style={{ width: "100%" }}
id={this.props.label}
label={"example"}
variant="outlined"
error={!this.state.isValid && this.state.searchValue != ""}
onChange={event => {
console.log("changed");
const { value } = event.target;
this.setState({ searchValue: value }, () => {
this.isValid();
});
}}
/>
);
}
}
export default App;
Check this sandbox for a working example

Related

Display info when no input in react input

I want to create an input field in React.
It basically should display the entered input real-time (managed this part).
However, it also should display a message "no data provided!" when nothing was entered.
My if statement isn't working? Why?
import React from "react"
import ReactDOM from "react-dom"
class Exercise1 extends React.Component {
constructor() {
super()
this.state = {
firstName:""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange (event) {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
let display
if(this.state.firstname != "") {
display=this.state.firstName
} else {
display="no data provided!"
}
return (
<div>
<form>Input:
<input
type="text"
name="firstName"
placeholder = "no data provided!"
value={this.state.firstName}
onChange={this.handleChange}
/>
</form>
<h1>{display}</h1>
</div>
)
}
}
export default Exercise1
PS: please stick with your answer as much as possible to the code above since I am a beginner and can't follow too different approaches.
You have a typo here. Your state variable is firstName (with capital N), but you are trying to check condition with firstname (with small n). You should do this,
if(this.state.firstName != "") {
display = this.state.firstName
} else {
display = "no data provided!"
}
Demo
Hi you can use your if like this
import React from "react"
import ReactDOM from "react-dom"
class Exercise1 extends React.Component {
constructor() {
super()
this.state = {
firstName:""
}
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
const { firstName } = this.state
return (
<div>
<form>Input:
<input
type="text"
name="firstName"
placeholder = "no data provided!"
value={this.state.firstName}
onChange={this.handleChange}
/>
</form>
<h1>{firstName ? firstName : "no data provided!"}</h1>
</div>
)
}
}
export default Exercise1

Changing the state of a checkbox with setState

I have a component and i am able to change the state of checkbox item when i click on a button.
I also want to be able to change the state in another button-click, which has lot more logic in it.
The issue i am having in that when the code goes into the condition it does not set the state back to false.
I tried changing the state again using setstate but it does not change the state of the enabledCheckBox.
this.setState({
enabledCheckBox: !this.state.enabledCheckBox,
})
Can anyone tell me what the issue is?
Thanks
class Customer extends Component {
constructor(props) {
super(props)
this.state = {
...props,
enabledCheckBox: false
};
}
//this works
onChangeCheckBox=()=>{
this.setState({
enabledCheckBox: !this.state.enabledCheckBox,
})
}
//cant get this to work
handleCustomerClick = (event) => {
if (this.state.enabledCheckBox) {
this.setState({
enabledCheckBox: !this.state.enabledCheckBox,
})
}
https://codesandbox.io/s/1y0zywy727
I included a working example. It does similar things as how you described.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
enabledCheckBox: false
};
}
onClick = () => {
this.setState({ enabledCheckBox: !this.state.enabledCheckBox });
};
render() {
return (
<div>
<input
type="checkbox"
value={"some checked box"}
checked={this.state.enabledCheckBox}
onChange={this.onClick}
/>
{this.state.enabledCheckBox ? <div>Blue</div> : <div>Red</div>}
<button onClick={this.onClick}>Click to Change Color</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Both input and div are using this.state.enabledcheckbox. So if you check the box, not only the box will be checked but also the div's "color" would change with it. Same thing as clicking the button
your this is being bound incorrectly try this;
handleCustomerClick = (event) => {
let {enabledCheckBox} = this.state;
this.setState({
enabledCheckBox: !enabledCheckBox,
});
}
If you really need the if statement you could do the following;
handleCustomerClick = (event) => {
let {enabledCheckBox} = this.state;
let data;
if (enabledCheckBox) {
data = {
enabledCheckBox: !enabledCheckBox
}
}
this.setState(data);
}

React component structure and passing state upward

I have a few components that each contain inputs, and on the main component I have a button that will send that information all at once to the server. The problem is that the main component that has the button doesn't have the input content of the child components.
In the past I've passed down a method that would send the content back up into the state, but is there an easier less painful way of doing it? It just feels like an odd way of doing that.
Here's a short example of what I have and what I mean.
Main component:
import React from 'react';
import { Button } from 'react-toolbox/lib/button';
import Message from './Message';
class Main extends React.Component {
constructor() {
super();
this.state = { test: '' };
}
render() {
return (
<div className="container mainFrame">
<h2>Program</h2>
<Message />
</div>
);
}
}
export default Main;
And the message component:
import React from 'react';
import axios from 'axios';
import Input from 'react-toolbox/lib/input';
class Message extends React.Component {
constructor() {
super();
this.state = { message: '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.setState({ message: value });
}
render() {
return (
<Input
type="text"
label="Message"
name="name"
onChange={this.handleChange}
/>
);
}
}
export default Message;
To answer your question, yes. You can try using refs. Add a ref to Message component, and you will be able to access the child component's methods, state and everything. But thats not the conventional way, people generally use callbacks, as you mentioned earlier.
import React from 'react';
import { Button } from 'react-toolbox/lib/button';
import Message from './Message';
class Main extends React.Component {
constructor() {
super();
this.state = { test: '' };
}
clickHandler () {
let childState = this.refs.comp1.state //returns the child's state. not prefered.
let childValue = this.refs.comp1.getValue(); // calling a method that returns the child's value
}
render() {
return (
<div className="container mainFrame">
<h2>Program</h2>
<Message ref="comp1"/>
<Button onClick={this.clickHandler} />
</div>
);
}
}
export default Main;
import React from 'react';
import axios from 'axios';
import Input from 'react-toolbox/lib/input';
class Message extends React.Component {
constructor() {
super();
this.state = { message: '' };
this.handleChange = this.handleChange.bind(this);
}
handleChange(value) {
this.setState({ message: value });
}
getValue () {
return this.state.message;
}
render() {
return (
<Input
type="text"
label="Message"
name="name"
onChange={this.handleChange}
/>
);
}
}
export default Message;
You are doing what is suggested in docs so it's a good way.
I have a button that will send that information all at once to the server
I assume then it might be form you can use. If so you can just handle onSubmit event and create FormData object containing all nested input field names with their values (even in children components). No need for callbacks then.
handleSubmit(e){
e.preventDefault();
const form = e.currentTarget;
const formData = new FormData(form); // send it as a body of your request
// form data object will contain key value pairs corresponding to input `name`s and their values.
}
checkout Retrieving a FormData object from an HTML form

Passing state into child component as props not working

I'm trying to build an example CRUD app with React and React Router, and I can't figure out why state isn't passing into a child component the way I'm expecting it to. When I hit the edit route, it renders the Edit component, which grabs the kitten I want from the database and sends it's info to a Form component which is used both for editing an existing kitten or adding a new one.
Here's the Edit component:
import React, { Component } from 'react';
import axios from 'axios';
import { match } from 'react-router-dom';
import Form from './Form';
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
axios.get(`/updateKitten/${this.props.match.params.id}`)
.then(res => {
const kitten = res.data
this.setState({ kitten })
console.log(this.state.kitten.name) //Sammy, or something
})
.catch(err => console.log(err))
}
render() {
return (
<Form
name={this.state.kitten.name} // throws error or undefined
description={this.state.kitten.description} //throws error or undefined
route={this.props.match.params.id}
/>
)
}
}
The Edit component passes name, description, and route to this Form component:
import React, { Component } from 'react';
import axios from 'axios';
export default class Add extends Component {
constructor(props) {
super(props)
this.state = { name: this.props.name, description: this.props.description}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const name = e.target.name;
const value = e.target.value;
this.setState({
[name]: value
});
}
handleSubmit(e) {
axios.post(`/addKitten/${this.props.route}`, this.state)
.then(this.setState({ name: '', description: '' }))
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>Name</label>
<input type='text' name="name" value={this.state.name}
onChange={this.handleChange}/>
<label>Description</label>
<input type='text' name="description" value={this.state.description}
onChange={this.handleChange}/>
<input type='submit' value='Submit' />
</form>
)
}
}
And I get the following error:
bundle.js:28950 Uncaught TypeError: Cannot read property 'name' of undefined
from trying to send that info as props to the Form component.
What am I doing wrong?
Two things,
First: In your edit component, you have not initialised kitten state and you are setting it based on the API result. However, componentDidMount is called after the component has been called and hence the DOM has been rendered once and the first time it did not find any value as this.state.kitten.name or this.state.kitten.description .
Its only after the API is a success that you set the kitten state. Hence just make a check while rendering.
Second: You have console.log(this.state.kitten.name) after the setState function. However setState is asynchronous. See this question:
Change state on click react js
and hence you need to specify console.log in setState callback
You code will look like
export default class Edit extends Component {
constructor(props) {
super(props)
this.state = {}
}
componentDidMount() {
axios.get(`/updateKitten/${this.props.match.params.id}`)
.then(res => {
const kitten = res.data
this.setState({ kitten }. function() {
console.log(this.state.kitten.name) //Sammy, or something
})
})
.catch(err => console.log(err))
}
render() {
return (
<Form
name={this.state.kitten.name || '' }
description={this.state.kitten.description || ''}
route={this.props.match.params.id}
/>
)
}
}
When your Edit component loads for the first time, it doesn't have a kitten. Hence the error.
What you need to do is the create an empty kitten. In your Edit component...
this.state = { kitten: {name:''} }
This will set the kitten's name to an empty string on the very first mount/render of your component.

How to call methods in Parent component from Child component?

I have an element, a child component named <NormalTextField/>, but how can you call methods -- _handleFirstName and _handleLastName from its parent component <Home/>? I'm attempting to have the user enter in a text, and it would be sent off to create an object user in the Reducer that holds firstName and lastName.
In <Home/> I have the following:
_handleFirstName(event){
this.props.actions.updateFirstName(event.target.value)
}
_handleLastName(event){
this.props.actions.updateLastName(event.target.value)
}
render(){
return(
<NormalTextField
hint='Enter First Name'
onChange={this._handleFirstName.bind(this)}
value={this.props.user.firstName}
/>
<NormalTextField
hint='Enter Last Name'
onChange={this._handleLastName.bind(this)}
value={this.props.user.lastName}
/>
Then in my element <NormalTextField/>,
import React, { Component } from 'react'
import { TextField } from 'material-ui'
import { orange900 } from 'material-ui/styles/colors'
class TextFieldLabel extends Component {
static propTypes = {
hint: React.PropTypes.string,
onChange: React.PropTypes.func
}
_handle(event){
this.props.onChange(event.target.value)
}
render() {
var pr = this.props
var hint = pr.hint
var value = pr.value
return (
<TextField
hintText={hint}
onChange={this._handle.bind(this)}
value={value}
/>
)
}
}
export default NormalTextField
But once the user enters in a text, I get the following error: Uncaught: TypeError: Cannot read property 'value' of undefined for _handleFirstName(event). What am I doing wrong? Is this the right approach and is it possible for Child component to call Parent's methods?
The problem you're seeing is that you're passing event.target.value to _handleFirstName when it accepts event. You could just change _handle to this:
_handle(event) {
this.props.onChange(event)
}
Or, ideally, you could remove the event handler in your NormalTextField, and just use the onChange prop directly.
Start with moving the bind calls to the constructor:
constructor() {
super();
this._handleFirstName = this._handleFirstName.bind(this);
this._handleLastName= this._handleLastName.bind(this);
}
_handleFirstName(event){
this.props.actions.updateFirstName(event.target.value)
}
_handleLastName(event){
this.props.actions.updateLastName(event.target.value)
}
// remove .bind(this) from your onChange
render(){
return(
<NormalTextField
hint='Enter First Name'
onChange={this._handleFirstName}
value={this.props.user.firstName}
/>
<NormalTextField
hint='Enter Last Name'
onChange={this._handleLastName}
value={this.props.user.lastName}
/>
Change your NormalTextField to this:
class TextFieldLabel extends Component {
static propTypes = {
hint: React.PropTypes.string,
onChange: React.PropTypes.func
}
// _handle removed
render() {
var pr = this.props
var hint = pr.hint
var value = pr.value
return (
<TextField
hintText={hint}
onChange={this.props.onChange} // use prop directly
value={value}
/>
)
}
}

Categories