I'm new to React and Redux, so pardon me if the answer is trivial, but after an extensive search I don't seem to find a good answer to this simple question. I have multiple cascading selects where data getting populated based on the previous selection. Everything works fine when the user changes selected option. However, I can't figure out how to trigger onChange event when the data initially loaded in the first select? Here's the simplified component:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { locationActions } from '../../_actions';
import { Input, Col, Label, FormGroup } from 'reactstrap';
class HeaderSetup extends React.Component {
constructor(props) {
debugger;
super(props);
this.state = { location: 'Select an Option'};
}
componentWillReceiveProps(nextProps) {
if (nextProps.loading !== this.props.loading &&
nextProps.success !== this.props.success &&
!nextProps.loading && nextprops.success) {
this.setState({ location: '' });
}
}
onLocationChanged(e) {
console.log(e.target.value);
}
render() {
const { locations } = this.props;
return (
<FormGroup row>
<Label for="locations" sm={3}>Locations</Label>
<Col sm={8}>
{locations.items &&
<Input type="select" name="locations" id="locations"
onChange={this.onLocationChanged}
value={this.state.location}>
{locations.items.map((location, index) =>
<option key={location.id}>
{location.locationName}
</option>
)}
</Input>
}
</Col>
</FormGroup>
)
}
}
function mapStateToProps(state) {
debugger;
const { locations } = state;
return {
locations
};
}
export default connect(mapStateToProps)(HeaderSetup);
Do I just need to trigger it manually? If so, what's the best place/way to do that? Any help is greatly appreciated!
Since you are using controlled components they should always reflect the state. In your onChange callback you should just update the state and all the inputs should update accordingly.
If you put up a minimal working example showing this issue I might be able to provide more details.
Below is a simple working example of how to set this up:
class App extends React.Component {
state = {
locations: []
};
componentDidMount() {
setTimeout(() => { // simulate loading
this.setState({
loading: false,
locations: [
{
id: 1,
label: "Paris"
},
{
id: 2,
label: "Rome"
}
]
});
}, 3000);
}
render() {
return <MyForm locations={this.state.locations} initialLocation={2}/>;
}
}
class MyForm extends React.Component {
state = {
initialLocation: null,
location: ""
};
componentWillReceiveProps(nextProps) {
this.setState({
initialLocation: nextProps.initialLocation,
})
}
onChange = e => {
this.setState({
location: e.target.value
});
};
render() {
const { locations } = this.props;
return (
<label>
<div>Select a location:</div>
{locations.length > 0 && (
<select value={this.state.location || this.state.initialLocation} onChange={this.onChange}>
{locations.map(({ id, label }) => (
<option key={id} value={id}>
{label}
</option>
))}
</select>
)}
</label>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Related
So when I run this and check the checkbox, I can see the values changing in the state, but why is the checkbox control not changing its status from check/uncheck? I know the render() method is being hit as well. Why, oh why, Gods of code? Lost in hours of figuring out what's wrong and I'm lost!
bob-Todos.js FILE
class Todo extends React.Component {
constructor(param) {
super();
this.state = {
id: param.data.id,
text: param.data.text,
completed: param.data.completed,
onMyChange: param.OnChange,
};
}
render() {
console.log("In TODO Render");
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
this.state.onMyChange(this.state.id);
}}
checked={this.state.completed}
/>
{this.state.text}
</p>
</div>
);
}
}
export default Todo;
Bob-App.js FILE
import React, { Component } from "react";
import Todo from "./bob-Todo";
import todoData from "../data/bob-todosData";
class App extends Component {
constructor() {
super();
this.state = { data: todoData };
this.OnChange = this.OnChange.bind(this);
}
OnChange(myId) {
this.setState((prev) => {
let updatedTodos = prev.data.map((todo) => {
if (todo.id === myId) {
todo.completed = !todo.completed;
}
return todo;
});
return { data: updatedTodos };
});
console.log(this.state.data);
}
render() {
return this.state.data.map((item) => {
return <Todo key={item.id} data={item} OnChange={this.OnChange} />;
});
}
}
export default App;
bob-todosData.js FILE
const todosData = [
{
id: 1,
text: "take out the trash",
completed: true
},
{
id: 2,
text: "rest for a while and relax",
completed: false
},
{
id: 3,
text: "watch an online movie",
completed: true
}
]
export default todosData
index.js FILE
import React from 'react';
import ReactDOM from 'react-dom';
import AppBob from "./bobComponents/Bob-App";
ReactDOM.render(
<AppBob />, document.getElementById('root')
);
You don't need to assign your props to state in your Todo component
Just remove them and invoke the function also use those variables directly:
Then your component will be:
class Todo extends React.Component {
render() {
const {
data: {
id,
text,
completed,
},
OnChange, // <-- Should rename this to "onChange"
} = this.props;
console.log('In TODO Render');
return (
<div>
<p>
<input
type="checkbox"
onChange={() => {
OnChange(id);
}}
checked={completed}
/>
{text}
</p>
</div>
);
}
}
export default Todo;
Also, rename your OnChange function to onChange to enable js convention
I want to validate the value that the user write in the input.
The browser works, creating a new room with the click of a button works, but the input doesn't change color according to the validation I set, why?
Inside addRoomName function I created setState for the value inside the room input
addRoomName=(e)=> {
this.setState({ room: e.target.value })
and additionally I created setState for the validation with the conditions
this.setState({ addRoomName: e.target.value });
if (e.target.value.length >= 6){
this.setState({roomNameInputColor:'green'})
} else {
this.setState({roomNameInputColor:'red'})
}
Is that may be the problem? because it seems that the react don't even recognize the validation but just the first setState (the one that bring the value that wrote in the room input)
So why the input doesn't change color?
I shared all the code
thanks!
App.js
import React, { Component } from 'react'
import './App.css';
import Addroom from './components/Addroom.js'
import Room from './components/Room.js'
export default class App extends Component {
state = {
roomsList:[{room:'',color:''}],
}
create = (r, c) => {
this.setState({ roomsList: [...this.state.roomsList, { room: r, color: c }] })
}
render() {
return (
<div>
<h1>My Smart House</h1>
{this.state.roomsList.map((element) => {
return <Room r={element.room} c={element.color} />
})}
<Addroom add={this.create}/>
</div>
)
}
}
Addroom.js
import React, { Component } from 'react'
export default class Addroom extends Component {
constructor(props) {
super(props)
this.state = {
roomNameInputColor:'white',
}
}
addRoomName = (e) => {
this.setState({ room: e.target.value })
this.setState({ addRoomName: e.target.value });
if (e.target.value.length >= 6) {
this.setState({ roomNameInputColor: 'green' })
} else {
this.setState({ roomNameInputColor: 'red' })
}
}
addColor = (e) => {
this.setState({ color: e.target.value })
}
createRoom = () => {
this.props.add(this.state.room, this.state.color);
}
render () {
return (
<div>
<input onChange={this.addRoomName} style={{ backgroundInputColor: this.state.roomNameInputColor }} placeholder='Name Your Room'/>
<br/>
<input onChange={this.addColor} placeholder='Whats The Room Color?'/>
<br/>
<button onClick={this.createRoom}>Create</button>
</div>
)
}
}
Room.js
import React, { Component } from 'react'
export default class Room extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return (
<div>
<h1>Room: {this.props.r} </h1>
<h3>Color: {this.props.c} </h3>
</div>
)
}
}
In your addRoomName function, you are doing multiple setState in a row, where it's often a source of state confusions (that you are probably experiencing here).
Prefer to have a single call to the setState() method in your function like this:
addRoomName = (e) => {
const room = e.target.value;
let roomNameInputColor = '';
if (room.length >= 6) {
roomNameInputColor = 'green';
} else {
roomNameInputColor = 'red';
}
this.setState({ room, addRoomName: room, roomNameInputColor });
}
thanks everyone, now it works, I did like you send guys to have effective code and also I changed this
<input onChange={this.addRoomName} style={{backgroundInputColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
To this
<input onChange={this.addRoomName} style={{backgroundColor:this.state.roomNameInputColor}} placeholder='Name Your Room'/><br/>
Because backgroundColor is a reserved word and while I tried to fix the problem I didn't saw that little important thing.. thanks!
I have built this site
https://supsurvey.herokuapp.com/surveycreate/
now I am trying to move the fronted to React so I can learn React in the process.
with vanila js it was much easier to create elements dynamically.
I just did createElement and after that when I clicked "submit" button
I loop throw all the elements of Options and take each target.value input.
so I loop only 1 time in the end when I click Submit and that's it I have now a list of all the inputs.
in react every change in each input field calls the "OnChange" method and bubbling the e.targe.value to the parent and in the parent I have to copy the current array of the options and rewrite it every change in every field.
is there other way? because it seems crazy to work like that.
Options.jsx
```import React, { Component } from "react";
class Option extends Component {
constructor(props) {
super(props);
this.state = { inputValue: "", index: props.index };
}
myChangeHandler = event => {
this.setState({ inputValue: event.target.value });
this.props.onChange(this.state.index, event.target.value);
};
render() {
return (
<input
className="survey-answer-group"
type="text"
placeholder="Add Option..."
onChange={this.myChangeHandler}
/>
);
}
}
export default Option;
______________________________________________________________________________
Options.jsx````
```import React, { Component } from "react";
import Option from "./option";
class Options extends Component {
render() {
console.log(this.props);
return <div>{this.createOptions()}</div>;
}
createOptions = () => {
let options = [];
for (let index = 0; index < this.props.numOfOptions; index++) {
options.push(
<Option key={index} onChange={this.props.onChange} index={index} />
);
}
return options;
};
}
export default Options;```
______________________________________________________________________________
App.jsx
```import React from "react";
import OptionList from "./components/Options";
import AddButton from "./components/add-button";
import "./App.css";
class App extends React.Component {
state = {
numOfOptions: 2,
options: [{ id: 0, value: "" }, { id: 1, value: "" }]
};
handleChange = (index, value) => {
const options = [...this.state.options];
console.log("from App", value);
options[index].value = value;
this.setState({
options: options
});
console.log(this.state);
};
addOption = () => {
const options = [...this.state.options];
options.push({ id: this.state.numOfOptions + 1, value: "" });
this.setState({
numOfOptions: this.state.numOfOptions + 1,
options: options
});
};
submitButton = () => {};
render() {
return (
<div className="poll-create-grid">
<div id="poll-create-options">
<OptionList
onChange={this.handleChange}
numOfOptions={this.state.numOfOptions}
/>
</div>
<button
className="surveyCreate-main-btn-group"
onClick={this.addOption}
>
Add
</button>
<button
className="surveyCreate-main-btn-group"
onClick={this.submitButton}
>
Submit
</button>
</div>
);
}
}
export default App;
```
So firstly,
The issue is with the way your OptionList component is defined.
Would be nice to pass in the options from the state into the component rather than the number of options
<OptionList
onChange={this.handleChange}
options={this.state.options}
/>
The you basically just render the options in the OptionsList component (I'm assuming it's same as the Options one here
class Options extends Component {
...
render() {
return
(<div>{Array.isArray(this.props.options) &&
this.props.options.map((option) => <Option
key={option.id}
index={option.id}
onChange={this.props.onChange}
value={option.value}
/>)}
</div>);
}
...
}
You would want to use the value in the Option component as well.
this.props.onChange(this.state.index, event.target.value); No need using the state here to be honest
this.props.onChange(this.props.index, event.target.value); is fine
Refactoring ReactJS form to work with Vazco-Uniforms. Receiving error when executing SelectField onChange method:
Uncaught TypeError: Cannot read property 'value' of undefined # onConditionChange(event) {
this.setState({condition: event.target.value});
No errors when processing form in native Reactjs
import React from 'react';
import { Stuffs, StuffSchema } from '/imports/api/stuff/stuff';
import { Grid, Segment, Header } from 'semantic-ui-react';
import AutoForm from 'uniforms-semantic/AutoForm';
import TextField from 'uniforms-semantic/TextField';
import NumField from 'uniforms-semantic/NumField';
import SelectField from 'uniforms-semantic/SelectField';
import SubmitField from 'uniforms-semantic/SubmitField';
import HiddenField from 'uniforms-semantic/HiddenField';
import ErrorsField from 'uniforms-semantic/ErrorsField';
import { Bert } from 'meteor/themeteorchef:bert';
import { Meteor } from 'meteor/meteor';
class AddStuff extends React.Component {
constructor(props) {
super(props);
this.state = {
condition: ''
};
this.condition = React.createRef();
this.submit = this.submit.bind(this);
this.insertCallback = this.insertCallback.bind(this);
this.formRef = null;
}
insertCallback(error) {
if (error) {
Bert.alert({ type: 'danger', message: `Add failed: ${error.message}` });
} else {
Bert.alert({ type: 'success', message: 'Add succeeded' });
this.formRef.reset();
}
}
submit(data) {
const { name, quantity, condition } = data;
const owner = Meteor.user().username;
Stuffs.insert({ name, quantity, condition, owner }, this.insertCallback);
}
onConditionChange(event) {
this.setState({condition: event.target.value});
const condition = this.condition.current.value;
console.log("Condition changed to: " + condition)
}
render() {
return (
<Grid container centered>
<Grid.Column>
<Header as="h2" textAlign="center">Add Stuff</Header>
<AutoForm ref={(ref) => { this.formRef = ref; }} schema={StuffSchema} onSubmit={this.submit}>
<Segment>
<TextField name='name'/>
<NumField name='quantity' decimal={false}/>
<SelectField name='condition' ref={this.condition} value={this.state.condition} onChange={this.onConditionChange.bind(this)}/>
<SubmitField value='Submit'/>
<ErrorsField/>
<HiddenField name='owner' value='fakeuser#foo.com'/>
</Segment>
</AutoForm>
</Grid.Column>
</Grid>
);
}
}
export default AddStuff;
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { Tracker } from 'meteor/tracker';
const Stuffs = new Mongo.Collection('stuff');
const StuffSchema = new SimpleSchema({
name: String,
quantity: Number,
owner: String,
condition: {
type: String,
allowedValues: ['excellent', 'good', 'fair', 'poor', 'sucks'],
defaultValue: 'good',
},
}, { tracker: Tracker });
Stuffs.attachSchema(StuffSchema);
export { Stuffs, StuffSchema };
I expect the result of selecting a value from SelectField name='condition' onChange to update the state of "condition" with the current selection provided by SimpleSchema.
The SelectField does not fire a regular onChange event with an Event as a parameter, but instead with the event.target.value:
<select
className="ui selection dropdown"
disabled={disabled}
id={id}
name={name}
onChange={event => onChange(event.target.value)}
ref={inputRef}
value={value}
>
https://github.com/vazco/uniforms/blob/8b1a1a28a85909df13a84f3ec33f7addb854b905/packages/uniforms-semantic/src/SelectField.js#L50
So you should be fine with:
onConditionChange(value) {
this.setState({condition: value});
}
onChangeModel worked perfectly capturing all form updates. I was also able to use onChange with conditions wrapped in AutoForm.
Re: link to full Q&A thread
<AutoForm
ref={(ref) => { this.formRef = ref; }}
schema={StuffSchema}
onSubmit={this.submit}
onChange={(key, value) => {
if (key === 'condition' && value === 'good') {
alert("Condition Good")
} else {
alert("Condition Not Good")
}
console.log(key, value)} }
>
the semantic-ui-react Dropdown object does not accept a name or id attribute, and therefore the change cannot be handled in the same way as other form elements. the docs show this:
import React, { Component } from 'react'
import { Dropdown, Grid, Segment } from 'semantic-ui-react'
const options = [
{ key: 1, text: 'One', value: 1 },
{ key: 2, text: 'Two', value: 2 },
{ key: 3, text: 'Three', value: 3 },
]
export default class DropdownExampleControlled extends Component {
state = {}
handleChange = (e, { value }) => this.setState({ value })
render() {
const { value } = this.state
return (
<Grid columns={2}>
<Grid.Column>
<Dropdown
onChange={this.handleChange}
options={options}
placeholder='Choose an option'
selection
value={value}
/>
</Grid.Column>
<Grid.Column>
<Segment secondary>
<pre>Current value: {value}</pre>
</Segment>
</Grid.Column>
</Grid>
)
}
}
when combining inputs into a single event handler, there's no tidy way to pull out an identifier to update the state for the dropdown. how is this normally handled?
thanks
One option is to use a simple wrapper(not unnecessary bloated) over different input controls, so that even if we change a control library we will have limited change scope. Below is simple example of such wrapper, and shows a simple approach to use same value change handler for multiple fields (even for different type of input controls):
import React, { Component } from 'react';
import { render } from 'react-dom';
const FIELD_NAMES = {
FirstName: 'FirstName',
LastName: 'LastName',
};
const TEXT_CONTAINER_STYLE = { padding: 5 };
function MyTextInput(props) {
const {
name,
onChange,
value,
} = props;
function handleValueChange(e) {
onChange(name, e.target.value);
}
return (
<div style={TEXT_CONTAINER_STYLE}>
<input onChange={handleValueChange} value={props.value} />
</div>
);
}
class App extends Component {
constructor() {
super();
this.state = {
};
this.state[FIELD_NAMES.FirstName] = '';
this.state[FIELD_NAMES.LastName] = '';
}
handleValueChange = (fieldName, fieldValue) => {
if (fieldName) {
let newState = {};
switch (fieldName) {
case FIELD_NAMES.FirstName:
newState[FIELD_NAMES.FirstName] = fieldValue;
break;
case FIELD_NAMES.LastName:
newState[FIELD_NAMES.LastName] = fieldValue;
break;
}
this.setState(newState);
}
}
getFieldValue = (fieldName) => {
return this.state[fieldName]
}
render() {
return (
<div>
<MyTextInput
name={FIELD_NAMES.FirstName}
value={this.getFieldValue(FIELD_NAMES.FirstName)}
onChange={this.handleValueChange}
/>
<MyTextInput
name={FIELD_NAMES.LastName}
value={this.getFieldValue(FIELD_NAMES.LastName)}
onChange={this.handleValueChange}
/>
<div>
{`First Name : ${this.getFieldValue(FIELD_NAMES.FirstName)}`}
</div>
<div>
{`Last Name : ${this.getFieldValue(FIELD_NAMES.LastName)}`}
</div>
</div >
);
}
}
render(<App />, document.getElementById('root'));
Working example