Can't get attributes of material-ui SelectField in react - reactjs

I'm using SelectField of material-ui for my react project.
I have tried many ways from this answer Can't get the target attributes of material-ui select react component
.
But they don't work.My target.id always equals ""
How can I get the attributes (like id).
Here is my code:
constructor(props) {
super(props);
this.state = {
form: {
resident_city: ''
},
ret_code: '',
ret_msg: ''
};
this.handleList = this.handleList.bind(this);
}
handleList(event, index, value) {
event.persist()
const field = event.target.id;
const form = this.state.form;
form[field] = value;
console.log(event, value)
this.setState({
form
});
console.log(form);
}
<form>
<SelectField
style={style}
id="city"
value={this.state.form.resident_city}
onChange={this.handleList}
maxHeight={200}
>
{cities}
</SelectField>
</form>
Update
I tried to use SelectField without form,and I still can't get the id attributes.It is really confusing me.

On the main component you define a prop name for select the form component let say your city component is called : cityForm
in your cityForm component
render() {
return (
<SelectField
style={style}
value={this.props.city}
onChange={(e, index, value) => this.props.selectChange(e, index, value, this.props.cityName)}
maxHeight={200}
>
{cities}
</SelectField>
);
}
}
In your main comp you will have let say (code is cutted some part omitted)
handleSelectChange(e, index, value, name){
this.setState({
[name] : value,
});
}
render(){
return (
<cityForm cityName = "city1" city={this.state.city1} selectChange={this.handleSelectChange}/>
);
}
}
Im building a dynamic form generator and it did the trick for me =).

If a React class component is used, selected value can be accessed through its state object.
Alternatively, with help of Redux the Select's onChange method can dispatch an action and update the value in the main application state.
In cases when updating the main state isn't feasible and function component is chosen instead of a class component, getting the value from the component outside the function becomes cumbersome.
An easy way to fix it would be to add a hidden input referencing the same value as select uses. Consider the following piece of code. It uses Selector field1 to update value through internal component state and binds it to the hidden field theField. The value can be further read an outside function during dispatch just like any other input field value in the form.
import React, { useState } from 'react';
import { Button, FormControl, MenuItem, Select } from '#material-ui/core';
import { connect } from 'react-redux';
const mapStateToProps = () => ({
});
const mapDispatchToProps = (dispatch, ownProps) => ({
submitForm: (event) => {
event.preventDefault();
console.log(event.target.theField.value);
dispatch({
type: 'UPDATE_THE_FIELD',
theField: event.target.theField.value,
});
}
});
function Main(props) {
const [field1, setField1] = useState("v1");
return (
<>
<form onSubmit={(event) => { props.submitForm(event, props) }}>
<FormControl>
<Select
value={field1}
onChange={(event) => setField1(event.target.value)}
>
<MenuItem value="v1">V1</MenuItem>
<MenuItem value="v2">V2</MenuItem>
</Select>
</FormControl>
<Button type="submit">Update</Button>
<input type="hidden" name="theField" value={field1} />
</form>
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Main);

Related

ReactJS Error: Maximum update depth exceeded

I have a LessonEditor component that passes down to children components props with a function called setValues:
const [values, setValues] = React.useState({
title: 'This is a long lesson title lol lol',
desc: 'This is a description',
location: '1',
content: 'I\'m a poteto',
});
const handleChange = (name, value) => {
console.log(name + value);
setValues({ ...values, [name]: value });
};
The first component to which I pass props is:
<LessonContents
editing={editing}
title={values.title}
desc={values.desc}
content={values.content}
location={values.location}
setValues={handleChange} // right here
/>
Then inside LessonContents I have another component where I pass the setValues function:
<TextEditor
content={props.content}
setContent={props.setValues}
/>
Then finally inside the TextEditor the code is:
import React from 'react';
export default function TextEditor(props) {
return (
<div
suppressContentEditableWarning={true}
id='editor'
contentEditable
onKeyDown={(e) => props.setContent('content', e.target.innerHTML)}
>
{props.content}
</div>
)
}
The error is triggered when I try to type text in the contentEditable div. Why?
EDIT: now I just realized that also the input text fields are causing the error, but if I delete this code contained in LessonContent.js the input text are working again:
<div className='box-content justify-left'>
<LessonLocation
location={props.location}
setLocation={props.setValues}
/>
</div>
<div className='box-content justify-left'>
<TextEditor
content={props.content}
setContent={props.setValues}
/>
</div>
I don't know exactly why this is happening, but if I disable updating on your LessonLocation component, the error stops:
export default class LessonLocation extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
const data = {
label: 'hello',
value: 'hello'
}
return <Tree data={data}></Tree>
}
};
It has to do with the <Tree /> updating. I don't know why it even updates, it doesn't seem like it needs the data you use. You should try to implement a check into shouldComponentUpdate() yourself if you need it to update based on the data.

Trouble updating radio button state value in parent component from child element

I'm currently working a a multipage checklist app to make a common checklist procedure more efficient.
my parent component called MainForm has all of the states for my app. In my first child element, I had to fill some text inputs. The states are updating and saving as planned. My second page (or other child element) was the portion where my checklist would begin. The issue is my app is rending, but the radiobutton value isn't being sent to my state. I'm also having an issue where I can select the 'yes' radio button and then the 'no' radio button, but I can't go from 'no' to 'yes'. radioGroup21 is the radio group that's giving me problem. All other states are working.
I'm getting an error in my console that says:
"Checkbox contains an input of type radio with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props.
I've tried removing the value tag and the defaultValue line in my Radio elements, but no luck. I've tried creating constructor(props) in my parent element but I still kept having issues."
So far I've tried removing the defaultValue in my radio button and after I tried removing the value line. Unfortunately I this did not help.
I also read about controlled and uncontrolled inputs. I've tried changing my parent components state to put them in a constructor(props) bracket. But no luck.
I also tried to not use the handleChange function and use the setState function with values of {radioButton21 === 'yes'} but that didn't work.
//Parent Component
Class MainForm extends Component {
state = {
step: 1,
projectNumber: '',
projectName: '',
numberOfSystems: '',
buildSheet: '',
controlPhilosophy: '',
projectLayoutDrawing: '',
projSoftwareValidation: '',
CppDrawing: '',
radioGroup21: '',
}
nextStep = () => {
const { step } = this.state
this.setState({
step : step + 1
})
}
prevStep = () => {
const { step } = this.state
this.setState({
step : step - 1
})
}
handleChange = input => event => {
this.setState({ [input] : event.target.value })
}
render(){
const {step} = this.state;
const { projectNumber, projectName, numberOfSystems, buildSheet , controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 } = this.state;
const values = { projectNumber, projectName, numberOfSystems, buildSheet, controlPhilosophy, projectLayoutDrawing, projSoftwareValidation, CppDrawing, radioGroup21 };
switch(step) {
case 1:
return <ProjectInfo
nextStep={this.nextStep}
handleChange = {this.handleChange}
values={values}
/>
case 2:
return <PrelimInspection
nextStep={this.nextStep}
prevStep={this.prevStep}
handleChange = {this.handleChange}
values={values}
/>
export default MainForm;
-----------------------------------
//Child Component
import React, { Component } from 'react';
import { Form, Button, Radio } from 'semantic-ui-react';
import { throws } from 'assert';
class PrelimInspection extends Component{
saveAndContinue = (e) => {
e.preventDefault();
this.props.nextStep();
}
back = (e) => {
e.preventDefault();
this.props.prevStep();
}
render(){
const { values } = this.props
return(
<Form color='blue' >
<h1 className="ui centered">System Installation</h1>
<Form.Field inline>
<Form.Field>System Properly Supported</Form.Field>
<Radio
label = {'Yes'}
name = {'radio21'}
value = {'Yes'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
<Radio
label = {'No'}
name = {'radio21'}
value = {'No'}
onChange={this.props.handleChange('radioGroup21')}
defaultValue={values.radioGroup21}
/>
</Form.Field>
<Button onClick={this.back}>Back</Button>
<Button onClick={this.saveAndContinue}>Save And Continue </Button>
</Form>
)
}
}
export default PrelimInspection
The app is rendering and the layout is correct. Unfortunately the state values aren't being sent to the parent state.
I checked the documentation https://react.semantic-ui.com/addons/radio/#types-radio-group and I have found few things you missed:
1.) Radio component asked the checked props (but you did not supply it).
2.) Which then requires you to pass the value, in your case it should come from the parent component:
<PrelimInspection
valueFromParent={this.state["radioGroup21"]}
nextStep={this.nextStep}
handleChange={this.handleChange}
values={values}
/>
so in your Child Component' render, take the value:
render() {
const { values, valueFromParent } = this.props;
...
3.) Radio's onChange value is passed as the second param (obj.value).
<Radio
label={'Yes'}
name={'radio21'}
value={"Yes"}
checked={valueFromParent === 'Yes'}
onChange={this.props.handleChange("radioGroup21")}
...
/>
So you can take the selected value like this:
// MainForm
handleChange = input => (event, obj) => { // obj is the second param
console.log("sendin here", input, obj.value);
this.setState({ [input]: obj.value });
};

How to dispatch action in event handler in React & Redux application

My code is here: https://stackblitz.com/edit/react-8g3p3l
There are 2 dumb components:
insertion_citylist.jsx with the following code:
insertion_shoplist.jsx with the following code:
I expect the change on the selected city from the inseriton_citylist.jsx will trigger change on the shop list in insertion_shoplist.jsx. So my container component, app.js contains a function findShops, where the action shoplist(city) is called.
My container component code, app.jsx, is the following:
import React, { Component } from "react";
import { connect } from "react-redux";
import { citylist, shoplist } from "../actions";
import { bindActionCreators } from "redux";
import CityList from "../components/insertion_citylist";
import ShopList from "../components/insertion_shoplist";
class App extends Component {
componentDidMount() {
console.log("mount");
this.props.citylist();
this.props.shoplist();
}
findShops(city) {
console.log("findShops:", city);
this.props.shoplist(city);
}
/* renderShops = shops =>
shops
? shops.map(shop => <option key={shop.id} value={shop.name} />)
: null; */
render() {
console.log("render:" + this.props);
return (
<div>
<CityList data={this.props.data} findShops={this.findShops.bind(this)} />
<ShopList {...this.props} />
{/* <input list="shop" placeholder="shop" />
<datalist id="shop">
{this.renderShops(this.props.shop_data.shops)}
</datalist> */}
</div>
);
}
}
const mapStateToProps = state => {
console.log("map state to props:" + state.shops);
return { data: state.insertion_data }; //Use of spread operator: https://www.youtube.com/watch?v=NCwa_xi0Uuc&t=1721s
//return { city_data: state.cities, shop_data: state.shops };
};
const mapDispathToProps = dispatch => {
console.log("map dispatch to props");
return bindActionCreators({ citylist, shoplist }, dispatch);
};
export default connect(
mapStateToProps,
mapDispathToProps
)(App);
The `findShops` can be called successfully when the city from the downdown list is changed, but seems that the `this.props.shoplist()` just called the action without calling the *reducer*. And the actions code from `actions/index.js` looks like this:
export function citylist() {
return { type: "CITY_LIST", payload: [{city:"Berlin"}, {city: "Frankfurt"}] };
}
export function shoplist(city) {
let data = [{name:"original"},{name:"original 2"}];
if (city === 'Frankfurt') data=[{name:"new"},{name:"new 2"}];
return {
type: "SHOP_LIST",
payload: data
};
}
Problem: The current code is able to trigger the event handler findShops(), but does not succeed in changing the city list state and thus the city list on the insertion_citylist.jsx just keeps unchanged all the while. Could anyone help on this?
Thanks in advance!
it turns out very simple issue
onChange={(e) => props.findShops(e)}
here you are passing e as paramenter
findShops(city) {
this.props.shoplist(city);
}
and using it directly, this is invalid as you will have event in your city parameter.
you need e.target.value
change
<select name="city"
onChange={(e) => props.findShops(e)}>{renderCities(props.data.cities)}</select>
to
<select name="city"
onChange={(e) => props.findShops(e.target.value)}>{renderCities(props.data.cities)}</select>
Demo,
remember react pass an event when you change the value, you need to extract the value when you are reading from it. like you are doing in javascript or jquery.
for checkboxes its e.target.checked and for inputs its e.target.value.

Reactjs - Update state of parent after obtaining calculated value from another component function

In my React App, I have a text box where users can type location in a Google Autocomplete box - LocationInput
import React, { Component } from 'react'
...
import LocationInput from './location-input'
...
change = (what, e) => this.setState({ [what]: e.target.value })
....
render() {
let {
location,
country,
...
} = this.state
...
...
<LocationInput value={location} change={this.change} />
You pass the new location to the change handler:
onPlaceSelected={(place) => {
change(getDistrict(place))
}}
This will call the change function in the outer component and update the state. It shouldn't cause an infinite loop unless for some reason onPlaceSelected gets called again automatically.
why not just have the change function work like:
change = location => this.setState({ location })
....
render() {
return <LocationInput value={this.state.location} change={this.change} />
}
and then in the child
const LocationInput = ({ value, change }) => {
return (
<div className="edit_location_div">
<Autocomplete
className="locationInput my2"
onPlaceSelected={ place => {
change(getDistrict(place))
}}
placeholder="Enter Nearest MAJOR City"
/>
</div>
)
}
of course I am making the assumption that the "onPlaceSelected" returns the same data-type as the one stored in the state.

Formsy-material-ui do not validate initial render

Is there any way, one can delay first validation of components in formsy-material-ui so that validations like isNotEmpty do not fire on first render of the form and mess the UX? I am using controlled components, therefore setting value from state on each render.
<FormsyText
name="name"
value={this.state.name}
floatingLabelText="Name"
onChange={partial(this._changeInputValue, ['name'])}
validations={{ isNotEmpty }}
validationError="Field shoud not be empty"
/>
I needed this solution too. I've been looking into the source code of formsy-material-ui, and it seems that the text field is setting its value right before it's mounted. That's why the field is marked changed (aka not pristine) when the rendering happens, so the validation error is shown.
Anyways, I wrote a hackish solution using a higher order component. I've been testing with text fields only, but should work with any fields having this problem. The core concept: if the formsy field doesn't have a "validationErrors" prop, it's not showing any errors.
import React, { Component, PropTypes } from 'react';
export const preventFirstValidation = (FormsyField) => {
return class extends Component {
static propTypes = { preventFirstValidation: PropTypes.bool };
static defaultProps = { preventFirstValidation: true };
constructor(props) {
super(props);
this.state = { isChanged: false };
}
render() {
const { preventFirstValidation, ...fieldProps } = this.props;
return (
<FormsyField
{...fieldProps}
onChange={(evt, val) => {
if (!this.state.isChanged) this.setState({ isChanged: true });
if (this.props.onChange) this.props.onChange(evt, val);
}}
validationErrors={(this.state.isChanged || !preventFirstValidation) ? this.props.validationErrors : undefined}
/>
);
}
};
};
How to use it:
import { Form } from 'formsy-react';
import FormsyTextField from 'formsy-material-ui/lib/FormsyText';
const TextField = preventFirstValidation(FormsyTextField);
const MyForm = () => (
<Form>
{/* You're using the transformed field, exactly like before */}
<TextField
name = "some_field"
validationErrors={{ isRequired: 'This is really required!' }}
required
preventFirstValidation={ /* you can enable or disable this feature */ }
/>
</Form>
);

Resources