Why in this example child component changing parent component state? According to the Facebook(react.js) docs State is similar to props, but it is private and fully controlled by the component.
codepen example
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {data: this.props.data};
}
handleChange(event) {
let updatedData = Object.assign({}, this.state.data);
updatedData[event.target.name][event.target.dataset.lang] = event.target.value;
this.setState({
data: updatedData
});
}
render() {
return (
<form>
{Object.keys(this.props.data.titles).map((l, index) =>
<input type="text" name="titles" data-lang={l} value={this.state.data.titles[l]} onChange={this.handleChange.bind(this)} />
)}
</form>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [{"titles": {"en": "deluxe1500x930.jpg"}
}],
count: 1
};
}
render() {
return (
<div>
{Object.keys(this.state.images).map((x,index) =>
<div>
{this.state.images[x].titles.en}
<NameForm data={this.state.images[x]} />
<button onClick={(() => {this.setState({ count: 2 })}).bind(this)}>test</button>
</div>
)}
</div>
)
}
}
Because you set the state with this.props.data.
the this.props.data came from the parent, therefore when it's changing so the state changes as well.
The solution is simple, just set the state with new value (copied from this.props.data) by using the spread operator instead of using the same reference.
this.state = {data: ...this.props.data};
Related
so I was working on a basic Todo app using React.js and I was wondering why the todo component does not automatically re-render once the state changed (the state contains the list of todos- so adding a new todo would update this array)? It is supposed to re-render the Header and the Todo component of the page with the updated array of todos passed in as props. Here is my code:
import React from 'react';
import './App.css';
class Header extends React.Component {
render() {
let numTodos = this.props.todos.length;
return <h1>{`You have ${numTodos} todos`}</h1>
}
}
class Todos extends React.Component {
render() {
return (
<ul>
{
this.props.todos.map((todo, index) => {
return (<Todo index={index} todo={todo} />)
})
}
</ul>
)
}
}
class Todo extends React.Component {
render() {
return <li key={this.props.index}>{this.props.todo}</li>
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.addnewTodo = this.addnewTodo.bind(this);
}
addnewTodo = () => {
let inputBox = document.getElementById("input-box");
if (inputBox.value === '') {
return;
}
this.props.handleAdd(inputBox.value);
}
render() {
return (
<div>
<input id="input-box" type="text"></input>
<button type="submit" onClick={this.addnewTodo}>Add</button>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { todos: ['task 1', 'task 2', 'task 3']}
this.handleNewTodo = this.handleNewTodo.bind(this);
}
handleNewTodo(todo) {
let tempList = this.state.todos;
tempList.push(todo);
this.setState = { todos: tempList };
}
render() {
return (
<div>
<Header todos={this.state.todos} />
<Todos todos={this.state.todos} />
<Form todos={this.state.todos} handleAdd={this.handleNewTodo} />
</div>
)
}
}
You are not updating the state correctly.
You need to make a copy of the this.state.todos, add the new todo in the copied array and then call this.setState function
handleNewTodo(todo) {
let tempList = [...this.state.todos];
tempList.push(todo);
this.setState({ todos: tempList });
}
Notice that this.setState is a function
You're updating state incorrectly,
handleNewTodo(todo) {
let tempList = [...this.state.todos];
tempList.push(todo);
this.setState({ todos: tempList });
}
This is the correct syntax.
I was wondering why the child component with the changed value is not getting rendered here.
Isn't it a good idea to have a child handle its own changes or better to have the controller in the parent?
class App extends React.Component {
constructor() {
super();
this.state = {
todos: todosData
};
}
render() {
const todoItems = this.state.todos.map(item => (
<TodoItem key={item.id} item={item} />
));
return <div className="todo-list">{todoItems}</div>;
}
}
This is the Child TodoItem
class TodoItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isComp: {}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange() {
let tempObj = this.state.isComp;
tempObj.completed = !this.state.isComp.completed;
this.setState = { isComp: tempObj };
console.log(this.state.isComp);
}
render() {
this.state.isComp = this.props.item;
console.log(this.state.isComp);
return (
<div className="todo-item">
<input type="checkbox" checked={this.state.isComp.completed} />
<p>{this.props.item.text}</p>
</div>
);
}
}
As you can see the state is changed with handleChange() but this does not fire the render. I am also not too sure if another object can be assigned to an object of the state (let tempObj = thi.state.isComp).
The functionality I am trying to achieve is check and uncheck a box and render accordingly.
What is this?
this.setState = { isComp: tempObj };
I think it should be
this.setState({ isComp: tempObj });
I have a main class here that holds three states rendering a Form component:
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
render(){
return(
<div className="content">
<Form formState={this.state}/>
</div>
)
}
}
And then inside my Form component I have ff codes:
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.setState({ this.props.formState.fName: e.target.value })}
</form>
)
}
}
Upon running this codes, I got an error saying it cannot read the property 'fName' of null.
How do I properly pass one state to it's children component so it can modify the main one?
You can't edit parent's state directly from child component. You should define handlers that would change parent's state in parent component itself, and pass them to children via props.
Parent:
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
fNameOnChange = (value) => {
this.setState({fName:value})
}
render(){
return(
<div className="content">
<Form formState={this.state} fNameChange={this.fNameOnChange}/>
</div>
)
}
}
Child:
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.props.fNameChange(e.target.value))}
</form>
)
}
}
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
this._handleChange = this._handleChange.bind(this);
}
//dynamically update the state value using name
_handleChange(e) {
const { name, value } = e.target;
this.setState({
[name]: value
});
}
render(){
return(
<div className="content">
//You can pass state and onchange function as params
<Form formState={this.state} _handleChange={this._handleChange}/>
</div>
)
}
}
class Form extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<form>
<input placeholder="First Name"
defaultValue={this.props.formState.fName}
id="fName"
name="fName"
onChange={e => this.props._handleChange} />
</form>
)
}
}
You should pass a function as a prop to update the state of parent component. It is advised not to change props directly.
In your parent class write a function like:
onChangeHandler (data) {
this.setState({fname: data})
}
And pass it to the child component and you can call this method as
this.props.onChangeHandler(event.target.data)
from child component.
Thanks
I'm wondering why you want to pass a formState to the form itself. The form should manage is own state. It is not a good practice to do 2 way binding like the Form mutate the MainApp's state
However, if for some reasons you need a copy of you formState in your MainApp component, then your Form component should have an onSubmit callback method.
class MainApp extends React.Component{
constructor(props){
super(props);
this.state = {
form: null
}
}
render() {
return(
<div className="content">
<Form onSubmit={(form) => {
this.setState({ form });
})}/>
</div>
)
}
}
class Form extends React.Component{
constructor(props){
super(props);
this.state = {
fName: '',
lName: '',
email: ''
}
}
render(){
return(
<form onSubmit={e => this.props.onSubmit(this.state) }>
<input placeholder="First Name" value={this.props.formState.fName}
onChange={e => this.setState({ this.props.formState.fName: e.target.value })}
</form>
)
}
}
I have a simple component who show element onClick:
class MyComponent extends React.Component {
state = {
isVisible : false
}
render() {
const { isVisble } = this.state
return(
<div>
{isVisble ?
<div onClick={() => this.setState({isVisble: false})}>Hide</div> :
<div onClick={() => this.setState({isVisble: true})}>Show</div>}
</div>
)
}
}
I use this component three times in other component :
class MySuperComponent extends React.Component {
render() {
return(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
)}
}
I need to pass isVisible at false for all other component if one of have isVisible to true
How to do that ?
Thanks
You should have your component controlled, so move isVisble to props and and then assign it from MySuperComponent.
Also pass MyComponent a callback so it can inform the parent if it wants to change the state.
You'd want some data structure to store that states.
https://codepen.io/mazhuravlev/pen/qxRGzE
class MySuperComponent extends React.Component {
constructor(props) {
super(props);
this.state = {children: [true, true, true]};
this.toggle = this.toggle.bind(this);
}
render() {
return (
<div>
{this.state.children.map((v, i) => <MyComponent visible={v} toggle={() => this.toggle(i)}/>)}
</div>
)
}
toggle(index) {
this.setState({children: this.state.children.map((v, i) => i !== index)});
}
}
class MyComponent extends React.Component {
render() {
const text = this.props.visible ? 'visible' : 'hidden';
return (<div onClick={this.props.toggle}>{text}</div>);
}
}
React.render(<MySuperComponent/>, document.getElementById('app'));
You can check your code here, is this what you want.
example
I have two components and i need to transfer state from children component to the parent component
class Parent Component {
this.state = {text: hahaha}
this.props.action(text, data)
<Children Component />
<button onClick={this.props.action(text, data)}
}
class Children Component {
this.state = {date: 12.12.12}
}
Another little tricky it's i have redux-action in Parent Component, that takes two parameters text and date, in sum when i click button i need to transfer state from childComp to the parentComp and then create action with two parametres in parentComp. So how i can do that?
Refer component communication
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {
content: 'initial'
}
this.updateParentState = this.updateParentState.bind(this);
}
updateParentState(content){
this.setState({
content: content
})
}
render(){
let { content } = this.state;
return <div>
<Child updateParentState={this.updateParentState}/>
<h1>{ content }</h1>
</div>
}
}
class Child extends React.Component{
constructor(props){
super(props);
this.state = {
value: 'initial'
}
this.handleParentState = this.handleParentState.bind(this);
this.changeContent = this.changeContent.bind(this);
}
handleParentState(content){
let { updateParentState } = this.props;
let { value } = this.state;
updateParentState(content);
}
changeContent(event){
this.setState({
value: event.target.value
})
}
render(){
let { value } = this.state
return <div>
<input value={value} onChange={this.changeContent}/>
<button onClick={this.handleParentState}>Update Parent State</button>
</div>
}
}
You can get state of child in parent component wil call back:
class Parent extends React.Component{
constructor(){
super();
this.state = {
};
}
onClick(childState){
console.log(childState); //see child state in parent component
}
render(){
return <Child onClick={this.onClick} />;
}
}
class Child extends React.Component{
constructor(){
super();
this.state = {
first: "first",
second: "second"
};
}
render(){
return <div onClick={() => this.props.onClick({...this.state})}>Click me</div>;
}
}
Also You can use redux or ref.