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
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!
Components ->
Box
Todolist
Add
AddModal
Main component App
But it is not working that is when I add a new task. It does not get added properly.
I think I cannot use this.setstate twice in a function.
Hope I am correct
Here is given the main component.
App.js :
import React, { Component } from 'react';
import './App.css';
import Box from './Components/Box';
import Add from './Components/Add';
import Todolist from './Components/Todolist';
class App extends Component {
constructor(props) {
super(props);
this.state = {
lists: '',
inputValue: '',
itemArray: []
}
}
onAddTask = () => {
this.setState ({
lists: this.state.inputValue
});
const item = this.state.itemArray;
const title = this.state.lists;
item.push({ title })
this.setState(prevState => ({
itemArray: [...prevState.lists, title]
}))
}
updateInputValue = (event) => {
this.setState({
inputValue: event.target.value
});
}
render() {
let length = this.state.itemArray.length;
return (
<div className="App">
<Box createTodo = {
<div>
{this.state.itemArray.map((itemArr) => {
return (
<div className="box">
<Todolist tasks = {itemArr} />
</div>
)
})}
</div>
}>
</Box>
<Add addTask = {this.onAddTask} inputValues = {this.updateInputValue} inputV = {this.state.inputValue} />
</div>
);
}
}
export default App;
Your addTasks function is not correct, you are mixing up things here.
In your inputValue you save the current value from the input field right? So if you write the following
this.setState({
lists: this.state.inputValue
});
you set your todo list to this single value. And your todo list is not an array anymore.
Secondly, state is imutable. So if you write the following
this.state.itemArray.push({ title });
the state will not be updated. What you actually want is the following:
onAddTask = () => {
this.setState({
itemArray: [...this.state.itemArray, this.state.inputValue]
})
}
And I'm not sure what the lists property on the state is for. You don't use it anywhere besides in your onAddTask function. So I guess you can remove it.
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>
I'm trying to create a custom radio button. The issue that i'm facing is that i'm unable to uncheck the radio button when another radio button is clicked. Currently it behaves like a checkbox.
import {React, ReactDOM} from '../../shared/lib/react';
export default class RadioButton extends React.Component {
constructor(props) {
super(props);
this.state = {
checkedRadioBtn: false
};
this.toggleRadioBtn = this.toggleRadioBtn.bind(this);
};
toggleRadioBtn(){
this.setState({checkedRadioBtn: !this.state.checkedRadioBtn});
};
render() {
return (
<div className="radio-btn-group">
<div onClick={this.toggleRadioBtn} className={this.state.checkedRadioBtn ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
};
You need to have container for group of radio buttons. That container will maintain the state for all the radio buttons and handle check/uncheck for each option. Here is the sample code for that,
import React from 'react'
import ReactDOM from 'react-dom'
class RadioBtn extends React.Component{
constructor(props) {
super(props);
}
handleClick(){
this.props.handler(this.props.index);
}
render() {
return (
<div className="radio-btn-group" onClick={this.handleClick.bind(this)}>
<div className={this.props.isChecked ? "radiobtn checked" : "radiobtn unchecked"} data-value={this.props.value}></div>
<label>{this.props.text}</label>
</div>
);
}
}
class RadioGrp extends React.Component{
constructor() {
super();
this.state = {
selectedIndex: null,
selectedValue: null,
options: ["option 0","option 1","option 2","option 3"]
};
}
toggleRadioBtn(index){
this.setState({
selectedIndex: index,
selectedValue: this.state.options[index],
options: this.state.options
});
}
render() {
const { options } = this.state;
const allOptions = options.map((option, i) => {
return <RadioBtn key={i} isChecked={(this.state.selectedIndex == i)} text={option} value={option} index={i} handler={this.toggleRadioBtn.bind(this)} />
});
return (
<div>{allOptions}</div>
);
}
}
var app = document.getElementById('app');
ReactDOM.render(<RadioGrp />, app);
Since you're using a div for a custom checkbox that doesn't behave like a normal checkbox you should be checking value against the selected value.
toggleRadioBtn(e){
this.setState({checkedRadioBtn: e.target.value});
};
Another question that I have is that you are assuming a single checkbox here so I have to assume you have a calling component that returns multiple instances. If that is the case then you need to pass your onClick down so you can pass the value back up to the parent. Then pass the selected value back down.
This is an example that I have in my application.
var languges = this.props.languages;
var languageToSelect = this.state.selectedLanguage;
var handleChange = this.handleChange;
var languageRows = Object.keys(languges).map(function(key) {
var language = languges[key];
return <LanguageBlock
key={ key }
language={ language }
languageCode={ key }
checked={ languageToSelect === key }
handleChange={ handleChange }
/>;
});
In my use case I have multiple languages and onChange I pass the selected language back up then on rerender the selected language will be updated so the radio options will reflect the change.
handleChange: function handleChange(event) {
this.setState({ selectedLanguage: event.target.value });
},
The handle change just sets state for the new value. The language block itself is just a simple component so no need to make it a class/component.
const LanguageBlock = ({ checked, language, languageCode, handleChange }) => {
return (
<div className="truncification">
<input type="radio" name="lang" id={ 'lang_' + languageCode }
value={ languageCode } checked={ checked } onChange={ (evt) => { handleChange(evt); } } />
<label htmlFor={ 'lang_' + languageCode }>{ language }</label>
</div>
);
};