React.js can't change checkbox state - javascript

I created this simple TODO list, and when I want to check the checkbox I can't.
import React from 'react';
const TodoItem = React.createClass({
render() {
return (
<div>
<span>{this.props.todo}</span>
<input type="checkbox" checked={this.props.done} />
</div>
);
}
});
export default TodoItem;
The parent:
import React from 'react';
import TodoItem from './TodoItem';
import AddTodo from './AddTodo';
const TodoList = React.createClass({
getInitialState() {
return {
todos: [{
todo: 'some text',
done:false
},{
todo: 'some text2',
done:false
},
{
todo: 'some text3',
done:true
}]
};
},
addTodo (childComponent) {
var todoText = childComponent.refs.todoText.getDOMNode().value;
var todo = {
todo: todoText,
done:false
};
var todos = this.state.todos.concat([todo]);
this.setState({
todos:todos
});
childComponent.refs.todoText.getDOMNode().value = '';
},
render() {
var Todos = this.state.todos.map(function(todo) {
return (
<TodoItem todo={todo.todo} done={todo.done} />
)
});
return (
<div>
{Todos}
<AddTodo addTodo={this.addTodo}/>
</div>
);
}
});
export default TodoList;

When you haven't specified an onChange handler on your inputs React will render the input field as read-only.
getInitialState() {
return {
done: false
};
}
and
<input type="checkbox" checked={this.state.done || this.props.done } onChange={this.onChange} />

Just for others coming here. React now provides defaultChecked:
<label htmlFor={id}>
<input
id={id}
type="checkbox"
defaultChecked={input.props.checked}
// disabled={input.props.disabled}
/>
<span className="custom-checkbox"></span>
{restChildren}
</label>

This is one of the top hits on Google for "React Component not updating", so although the answer has been well accepted, I think it could be misleading.
The checkbox type doesn't require the onChange. I am not sure if this has changed since the answer was posted (current version of React is v15 - 2016-04-20).
See the following example of it working without an onChange handler (see JSBIN):
class Checky extends React.Component {
render() {
return (<input type="checkbox" checked={this.props.checked} />);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { checked: true };
}
render() {
return (
<div>
<a href="#" onClick={ () => { this.setState({ checked: !this.state.checked }); }}>Toggle</a>
<hr />
<Checky checked={this.state.checked} />
</div>
);
}
}
React.render(<App />, document.body);
Another side note - a lot of answers (for similar questions) simply recommend "defaultChecked" - although this works, it is usually a half measure.

Related

Child Component not updating after state changes

I am learning react, and I am making a simple ToDoApp. I set some todo data from a JSON file in the state of my App Component and use the values to populate a Child component. I wrote a method to be called each time the onChange event is fired on a checkbox element and flip the checkbox by updating the state. Thing is this code worked perfectly fine before, but it's not anymore. The state gets updated accordingly when I change the checkbox, but it doesn't update in the child element, I'd like to know why. Here's my code
App.js
import React from "react";
import TodoItem from "./TodoItem";
import toDoData from "./toDosData";
class App extends React.Component {
constructor() {
super();
this.state = {
toDoData: toDoData
};
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(key)
{
this.setState(prevState => {
let newState = prevState.toDoData.map(currentData => {
if(currentData.id === key)
currentData.completed = !currentData.completed;
return currentData;
});
return {toDoData: newState};
});
}
render() {
let toDoComponents = this.state.toDoData.map(toDoDatum =>
<TodoItem key={toDoDatum.id} details={{
key: toDoDatum.id,
text: toDoDatum.text,
completed: toDoDatum.completed,
onChange: this.handleOnChange
}} />);
return (
<div>
{toDoComponents}
</div>
);
}
}
export default App;
TodoItem.js
import React from "react";
class TodoItem extends React.Component {
properties = this.props.details;
render() {
return (
<div>
<input type="checkbox" checked={this.properties.completed}
onChange={() => this.properties.onChange(this.properties.key)}
/>
<span>{this.properties.text}</span>
</div>
)
}
}
export default TodoItem;
Thanks in advance.
Why do you need to assign your details prop to properties in your class? If you do that properties does not reflect the prop changes and your child component can't see the updates. Just use the props as it is:
render() {
const { details } = this.props;
return (
<div>
<input
type="checkbox"
checked={details.completed}
onChange={() => details.onChange(details.key)}
/>
<span>{details.text}</span>
</div>
);
}
}
Also, since you don't use any state or lifecycle method in TodoItem component, it can be a functional component as well.
const TodoItem = ({ details }) => (
<div>
<input
type="checkbox"
checked={details.completed}
onChange={() => details.onChange(details.key)}
/>
<span>{details.text}</span>
</div>
);
One more thing, why don't you pass the todo itself to TodoItem directly?
<TodoItem
key={toDoDatum.id}
todo={toDoDatum}
onChange={this.handleOnChange}
/>
and
const TodoItem = ({ todo, onChange }) => (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onChange(todo.id)}
/>
<span>{todo.text}</span>
</div>
);
Isn't this more readable?
Update after comment
const toDoData = [
{ id: 1, text: "foo", completed: false },
{ id: 2, text: "bar", completed: false },
{ id: 3, text: "baz", completed: false }
];
const TodoItem = ({ todo, onChange }) => (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onChange(todo.id)}
/>
<span>{todo.text}</span>
</div>
);
class App extends React.Component {
constructor() {
super();
this.state = {
toDoData: toDoData
};
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange(key) {
this.setState(prevState => {
let newState = prevState.toDoData.map(currentData => {
if (currentData.id === key)
currentData.completed = !currentData.completed;
return currentData;
});
return { toDoData: newState };
});
}
render() {
let toDoComponents = this.state.toDoData.map(toDoDatum => (
<TodoItem
key={toDoDatum.id}
todo={toDoDatum}
onChange={this.handleOnChange}
/>
));
return <div>{toDoComponents}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

"Cannot read property 'map' of undefined" within React, what's wrong here?

Trying to get my head around props so forgive me if its a silly mistake. I am trying to pass all of my data into one variable and pass that out into props (using {item.text} and {item.key}), however, my ".map" isn't picking up anything and there's a bunch of errors, what's wrong with my code?
The problem lays specifically here in this block of code
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
Here is the code in full
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoListt extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList(input) {
let listArray = this.state.list;
listArray.push(input);
var newItem = {
text: listArray,
key: Date.now()
};
this.setState(prevState => {
return {
list: prevState.list.concat(newItem)
};
});
this.setState({
list: listArray
})
}
createList(list) {
return <li>{list.text}</li>
}
render() {
var entries = this.state.list
var finalEntries = entries.props.map(this.createList)
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList(this.state.userInput)}>Press me</button>
<ul>
{this.testingSetup()}
</ul>
</div>
);
}
}
export default TodoListt;
You can use the spread operator to add to an existing array. Simply add a new object to the array in the state, and then clear the user input, ready for another item. Based on your code, here's a simple example of adding to a state list (haven't run myself, so just check for syntax errors and such):
import React from "react";
import "./App.css";
import { isTemplateElement } from "#babel/types";
class TodoList extends React.Component {
state = {};
constructor(props) {
super(props);
this.state = {
userInput: "",
list: [],
};
}
changeUserInput(input) {
this.setState({
userInput: input
})
}
addToList() {
const { list, userInput } = this.state;
// Add item to state list using spread operator and clear input
this.setState({
list: [...list, {text:userInput, key: Date.now()}],
userInput: ""
});
}
render() {
return (
<div className="to-do-list-main">
<input
onChange={(e) => this.changeUserInput(e.target.value)}
value={this.state.userInput}
type="text"
/>
<button onClick={() => this.addToList()}>Press me</button>
<hr/>
{/* For each item in the list, render the contents */}
{this.state.list.map(item => (
<div key={item.key}>
<h3>{item.text}</h3>
<p>Time: {item.key}</p>
</div>
))}
</div>
);
}
}
export default TodoList;

Search and then modify the result input

Goal: Search must work correctly and then be able to modify the found input. I'm trying to figure out why I can't do both.
Observation: the crazy thing I found is it works if I change the key from key={index} to key={variable.value} which doesn't make any sense.
Can someone tell me what I'm doing wrong, or if there's any way to do this better?
You'll understand better if you look at the codesandbox DEMO
index.js
import React from "react";
import ReactDOM from "react-dom";
import Input from './Input'
const dummyVariablies = [
{
name: 'barrack',
value: 11
},
{
name: 'putin',
value: 22
},
{
name: 'trump',
value: 33
}
]
class App extends React.Component {
state = {
search: ''
}
handleSearch = (e) => {
this.setState({
search: e.target.value
})
}
getFilteredVariables = (variables) => {
const { search } = this.state;
return variables.filter(
variable => variable.name.toString().toLowerCase().includes(search.toString().toLowerCase())
);
}
render() {
const variables = this.getFilteredVariables(dummyVariablies || [])
return (
<div>
Goal: Search must work correctly and then be able to modify the found input
<br /> <br /> <br />
Search: <input onChange={this.handleSearch} />
<br /> <br /> <br />
{variables.map((variable, index) => {
return <Input variable={variable} key={index} />
})
}
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Input.js
import React from 'react';
export default class Input extends React.Component {
state = {
name: '',
value: ''
}
componentDidMount() {
this.setState(
{
value: this.props.variable.value,
name: this.props.variable.name
}
)
}
// static getDerivedStateFromProps(nextProps) {
// return {
// value: nextProps.variable.value,
// name: nextProps.variable.name
// }
// }
handleChange = (e) => {
this.setState({ value: e.target.value });
}
render() {
const { value, name } = this.state;
return (
<div>
{name}
<input type="text"
value={value}
onChange={this.handleChange}
/>
</div>
);
}
}
I forked your sandbox here
As you can see I changed the input:
import React from "react";
export default class Input extends React.Component {
state = {
value: this.props.variable.value
};
handleChange = e => {
this.setState({ value: e.target.value });
};
render() {
return (
<div>
{this.props.variable.name}
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</div>
);
}
}
The problem with your code is that you're sending props to your Input but you're not handling the updates correctly. You're only doing it once, because componentDidMount runs only once when the component is loaded. I simplified it and I'm just passing the props down. This way the filtration works fine.
Keep in mind that if you want to save the values correctly after you edit the inputs you'll have to insert dummyVariablies inside the state of App and then use lifting state up from your Input component. Good example can be found here.

Clear input in stateless React component

I want to implement an X icon inside Input component that will clear the input field. I can easily do it if I control the state. But is it actually possible with stateless component?
I use react-semantic-ui, their stateful components have auto controlled state.
So I want to create an input that can be used like this:
//Controlled
class App extends React.Component {
state = {
value:''
}
onChange = (event, props) => {
this.setState({value: props.value});
}
onClearInput = () => {
this.setState({value: ''});
}
render() {
return (
<MyInput
clearable
value={this.state.value}
onChange={this.onChange}
onClearInput={this.onClearInput}
/>
)
}
}
Or
// Uncontrolled
class App extends React.Component {
onChange = (event, props) => {
doSomething(props.value);
}
render() {
return (
<MyInput
clearable
onChange={this.onChange}
/>
)
}
}
In the second example, clearable feature will not work because we're not controlling the value.
MyInput can be implemented like this:
import React from 'react';
import { Input } from 'semantic-ui-react';
import ClearIcon from './ClearIcon';
function MyInput(props) {
const prepareProps = {...props};
if (props.clearable) {
prepareProps.icon=<ClearIcon onClick={props.onClearInput} />;
delete prepareProps.clearable;
}
delete prepareProps.onClearInput;
return (
<div className="my-input">
<Input {...prepareProps} />
</div>
);
}
...etc.
My problems:
clearable feature must work in both controlled and uncontrolled manner.
clearable feature should not require a handler. It would be nice to just provide a prop and handle the render and behavior of the X button under the hood.
I don't see any way to make this work. Any ideas?
Allowing the user of your component to set the value via props and still being able to clear the input can be easily achieved, e.g. like this:
class MyInput extends React.Component {
constructor(props) {
super(props);
this.state = {value: props.value || ''};
}
handleChange = event => {
const { onChange } = this.props;
this.setState({ value: event.currentTarget.value });
onChange && onChange(event);
};
handleClear = () => {
const { onClearInput } = this.props;
this.setState({ value: "" });
onClearInput && onClearInput();
};
render() {
const { value } = this.state;
const { clearable, onChange, ...inputProps } = this.props;
const clearIcon = clearable && <ClearIcon onClick={this.handleClear} />;
return (
<div className="my-input">
<Input value={value} icon={clearIcon} onChange={this.handleChange} {...inputProps} />
</div>
);
}
}
You could even make it more composable by using an hoc or render props as proposed by #pkuzhel.
Look at this codesandbox example to see it in action.
#Andrey
Would you try this below code? and let me know if that resolves your issue.
import React, { Component } from 'react';
import { Input, Button } from 'semantic-ui-react'
class App extends Component {
clear = () => {
console.log(this.inputRef.target.value);
this.inputRef.target.value = '';
}
render() {
return (
<div className="App">
<Input placeholder='Search...' onChange={(input) => {input.persist(); this.inputRef = input}} />
<Button onClick={this.clear}>Clear</Button>
</div>
);
}
}

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

Categories

Resources