Update State of a Component from Another in ReactJS - javascript

I'm following this article (original implementation Sibling Sibling):
Update state cross component
The example works perfectly. But when I try to separate each class to each .js file, then using import/export to call/bind each other. It (the updating state) doesn't work anymore.
The structure like this:
Sibling1.js
import React, { Component } from 'react';
<-- some declare style -->
export function updateText(text) {
this.setState({text})
}
export class Sibling1 extends Component {
render() {
return (
<div>
<div style={{ ...style.topLabel, color: secondaryColor }}>I am Sibling 1</div>
<input style={style.textBox} type="text"
placeholder="Write text" onChange={(e) => updateText(e.target.value)} />
</div>
)
}
}
Example.js
import React, { Component } from 'react';
import * as sibling1 from './Sibling1'; //is this good?
import {Sibling1} from './Sibling1'; //is this good?
<-- some declare style, variable -->
class Sibling2 extends Component {
constructor(props) {
super(props)
this.state = {
text: "Initial State"
}
sibling1.updateText = sibling1.updateText.bind(this) //is this good binding?
}
render() {
console.log('Sibling2.state : ', this.state);
return (
<div>
<div style={{ ...style.topLabel, color: primaryColor }}>I am Sibling 2</div>
<div style={style.label}>{this.state.text}</div>
</div>
)
}
}
class Example3 extends Component {
render() {
return (
<div>
<Sibling1 />
<Sibling2 />
</div>
)
}
}
export default Example3;
I am simply expecting Sibling1 can change the state of Sibling2 (like the original implementation), but cannot.
I guess that my bind(this) doesn't bind the right context.
Can somebody tell me what are differences between the original implementation (article above) and my approach (separate to multi .js files)?

updateText() should be bound to a component. I'm not sure what you're trying to achieve here but updateText() might not work in Sibling1 if context changes.
You could try binding updateText() in both components (already bound in Sibling2).
import React, { Component } from 'react';
export function updateText(text) {
this.setState({text})
}
export class Sibling1 extends Component {
constructor() {
updateText = updateText.bind(this)
}
render() {
return (
<div>
<div style={{ ...style.topLabel, color: secondaryColor }}>I am Sibling 1</div>
<input style={style.textBox} type="text"
placeholder="Write text" onChange={(e) => updateText(e.target.value)} />
</div>
)
}
}
Usually state is controlled in parent component if two child components need to share the state and only the handler is passed down to the chilren.

React kinda forces you to use a one way data flow. So you can't just update the state of Sibling1 from within Sibling2.
As Dinesh Pandiyan mentions in his example that you normally would have a parent component that controls the state of both the siblings. Your code would then look like this:
Sibling1.js
import React, { Component } from 'react';
<-- some declare style -->
export class Sibling1 extends Component {
function updateText(text) {
// Use updateText function from props.
// Props are like state but not controlled by the component itself
// The value is passed to the component from outside
this.props.updateText(text)
}
render() {
return (
<div>
<div style={{ ...style.topLabel, color: secondaryColor }}>I am Sibling 1</div>
<input style={style.textBox} type="text"
placeholder="Write text"
onChange={(e) => this.updateText(e.target.value).bind(this)} />
</div>
)
}
}
Example.js
import React, { Component } from 'react';
import { Sibling1 } from './Sibling1'; // This is good.
import Sibling1 from './Sibling1'; // This is also possible if you use export default class instead of export class
<-- some declare style, variable -->
class Sibling2 extends Component {
// Use same function as in Sibling1.
function updateText(text) {
this.props.updateText(text)
}
render() {
return (
<div>
<div style={{ ...style.topLabel, color: primaryColor }}>I am Sibling 2</div>
<div style={style.label}>{this.props.text}</div> // changed state to props
</div>
)
}
}
class Example3 extends Component {
constructor(props) {
super(props);
this.state = {
text: "Initial state"
};
}
// Control state from parent component
function updateText(
this.setState({ text: text });
}
render() {
return (
<div>
<Sibling1 updateText={this.updateText.bind(this)}/>
<Sibling2 updateText={this.updateText.bind(this)} text={this.state.text} />
</div>
)
}
}
export default Example3;

Related

How to call an event handler function from App.js in two other components

I created a reset function in App.js and want to call it by an onclick in two other components. the problem is that it works in one component but doesn't in the other.
Here are the codes snippets
App.js
import React from 'react';
import Result from './components/Result';
import GeneralResult from './components/GeneralResult';
class App extends Component {
constructor(props) {
super(props);
this.state = {
result: '',
counter: 0,
}
}
// Reset function
handleReset=()=>{
this.setState({
result: '',
counter: 0,
)}
renderResult() {
return (
<div>
<Result reset={()=>this.handleReset()} />
<GeneralResult back={()=>this.handleReset()} />
</div>
);
}
Result.js
first component making use of reset()
function Result(props) {
return (
<div>
<span>
<button onClick={props.reset}>Replay</button>
</span>
</div>
);
}
export default Result;
GeneralResult.js
second component making use of the reset
import React, { Component } from 'react';
export default class GeneralResult extends Component {
render() {
return (
<React.Fragment>
<h2>Congratulations you won!</h2>
<span>
<button onClick={props.back}> Back to Question</button>
</span>
</React.Fragment>
);
}
}
You can pass the handler as props, and render the component from the parent class.
class Child extends Component {
render(){
return(
<button onClick = {this.props.onClick}></button>
)
}
}
export default Child;
import Child from 'path/to/child';
class Parent extends Component {
onClick = (e) => {
//do something
}
render () {
return(
<Child onClick = {onCLick}/>
)
}
}
Problem is that GeneralResult is class based component. so when you need to access props passed to it. you have to use this.props.
export default class GeneralResult extends Component {
render() {
return (
<React.Fragment>
<h2>Congratulations you won!</h2>
<span>
// you need to change "props.back"
// to "this.props.back"
<button onClick={this.props.back}> Back to Question</button>
</span>
</React.Fragment>
);
}
}

How do I access some data of a property I passed from parent component to child component?

I am learning React and I am trying to call a function in a child component, that accesses a property that was passed from parent component and display it.
The props receives a "todo" object that has 2 properties, one of them is text.
I have tried to display the text directly without a function, like {this.props.todo.text} but it does not appear. I also tried like the code shows, by calling a function that returns the text.
This is my App.js
import React, { Component } from "react";
import NavBar from "./components/NavBar";
import "./App.css";
import TodoList from "./components/todoList";
import TodoElement from "./components/todoElement";
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo(input) {
const newTodo = {
text: input,
done: false
};
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" />
<button
onClick={() => this.addNewTodo(document.getElementById("text"))}
>
Add new
</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
export default App;
This is my todoElement.jsx
import React, { Component } from "react";
class TodoElement extends Component {
state = {};
writeText() {
const texto = this.props.todo.text;
return texto;
}
render() {
return (
<div className="row">
<input type="checkbox" />
<p id={this.writeText()>{this.writeText()}</p>
<button>x</button>
</div>
);
}
}
export default TodoElement;
I expect that when I write in the input box, and press add, it will display the text.
From documentation
Refs provide a way to access DOM nodes or React elements created in the render method.
I'll write it as:
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.textRef = React.createRef();
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo() {
const newTodo = {
text: this.textRef.current.value,
done: false
};
const todos = [...this.state.todos, newTodo];
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" ref={this.textRef} />
<button onClick={this.addNewTodo}>Add new</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
In your approach, what you got as an argument to the parameter input of the method addNewTodo is an Element object. It is not the value you entered into the text field. To get the value, you need to call input.value. But this is approach is not we encourage in React, rather we use Ref when need to access the html native dom.

How to pass data from parent to child in react.js

I have a parent component which has 1 child. I am updating my child by passing data through props. initially, it works fine but when I click on a button and update the state using setState the child gets rendered with old values by the time setState is finished. I have solved it using componentWillReceiveProps in the child but is this the right way?
In the below code if I do setState in filterResults function it won't update the Emplist component .
import React, { Component } from 'react';
import {Search} from './search-bar'
import Emplist from './emplist'
class App extends Component {
constructor(props){
super(props);
this.emp=[{
name:'pawan',
age:12
},
{
name:'manish',
age : 11
}]
this.state={emp:this.emp};
this.filterResults=this.filterResults.bind(this);
}
filterResults(val)
{
if(this.state)
{
let filt=[];
filt.push(
this.emp.find(e=>{
return e.age==val
})
);
this.setState({emp:filt});
}
}
render() {
return (
<div className="App">
<Search filterResults={this.filterResults}/>
<Emplist emp={this.state.emp}/>
</div>
);
}
}
export default App;
EmpList Componet
import React,{Component} from 'react'
export default class Emp extends Component
{
constructor(props){
super(props);
this.emplist=this.props.emp.map(e=>{return <li>{e.name}</li>});
this.next=this.emplist;
}
componentWillReceiveProps(nextProps,nextState,prevProps,prevState,nextContext,prevContext){
// this.props.updated(this.props.empo);
this.next=nextProps.emp[0];
if(this.next)
this.emplist= nextProps.emp.map(e=>{return <li>{e.name}</li>});
}
render(){
if(!this.next)
return <div>name not found</div>
else
return (
<div>
<br/>
<p>The list is here</p>
<ul>
{this.emplist}
</ul>
</div>
)
}
}
If you want to pass from parent to child you can pass using props and if you wan t to do reverse than you can pass one function from parent to child and than use this passed function to send something back to parent.
child will look something like this
class Reciepe extends Component{
render(){
const { title, img, instructions } = this.props;
const ingredients=this.props.ingredients.map((ing,index)=>(<li key={index} >{ing}</li>));
return (
<div className='recipe-card'>
<div className='recipe-card-img'> <img src={img} alt={title}/> </div>
<div className='recipe-card-content'>
<h3 className='recipe-title'>Reciepe {title}</h3>
<ul> {ingredients} </ul>
<h4>Instructions:</h4>
<p>{instructions}</p>
</div>
</div>
)
}
}
parent will look something like this
class RecipeList extends Component{
render(){
return (
<div style={{'display':'flex'}}>
{this.props.recipes.map((item,index)=>(
<Recipe key={index}
title={item.title}
ingredients={item.ingredients}
instructions={item.instructions}
img={item.img}
/>
))}
</div>
)
}
}
The problem is that you are assigning the values to this which is not a good practice. Check where to declare variable in React here.
If you are not using the props to do any complex operations. This should work.
EmpList Componet
import React, {Component} from 'react'
export default class Emp extends Component {
constructor(props) {
super(props);
}
render() {
if (!this.next)
return <div>name not found</div>;
else
return (
<div>
<br/>
<p>The list is here</p>
<ul>
{this.props.emp && this.props.emp.map(e => <li>{e.name}</li>)}
</ul>
</div>
)
}
}
Your next and emplist class properties are directly derivable from your props and hence you don't actually need them. You could do it in the following way
import React,{Component} from 'react'
export default class Emp extends Component{
render(){
const { emp } = this.props;
if(!emp || emp.length === 1)
return <div>name not found</div>
else {
return (
<div>
<br/> <p>The list is here</p>
<ul>
{emp.map(e=>{return <li>{e.name}</li>});}
</ul>
</div>
)
}
}
}
However in cases when you do what to make really complex decisions based on props, a combination of componentWillReceiveProps and componentDidMount/componentWillMount is the right place to do it.

How can I change a specific component in React on an eventHandler

What i'm trying to do should be fairly simple but it seems I am not able to get reference to the specific component using this
So here I have my App.js
import React, { Component } from 'react';
import CoolBox from './coolBox.js';
import './App.css';
class App extends Component {
changeColor(){
$(this).css('background','blue');
}
render() {
return (
<div className="App">
<CoolBox changeColor={function(){ this.changeColor() }.bind(this)} />
<CoolBox changeColor={function(){ this.changeColor() }.bind(this)} />
<CoolBox changeColor={function(){ this.changeColor() }.bind(this)} />
</div>
);
}
}
export default App;
And then here is CoolBox.js which is just a simple box with a background of red:
import React, { Component } from 'react';
import $ from 'jquery';
class CoolBox extends Component {
render() {
return (
<div onClick={this.props.changeColor} className="box"></div>
);
}
}
export default CoolBox;
Which simply looks like this:
Now what I am trying to achieve is when you click on any of the 3 boxes the background color will change just on that specific box that was clicked.
It seems I cannot use any jquery methods if $(this) cannot be referenced. So how can I achieve this simple function within React?
You don't need jQuery for this.
There are couple of way to reference components in the DOM and there are couple of patterns of such components(controlled and uncontrolled) you should read about it.
As for you solution, this is a simple solution just to get you start with.
On event handlers you can access the event as an argument.
changeColor(e) as e is the object that holds the event information as well as the target (the div you clicked in your case).
So basically what you can do in App.js is this:
class App extends React.Component {
constructor(props){
super(props);
this.changeColor = this.changeColor.bind(this);
}
changeColor(e){
e.target.style.background = "blue";
}
render() {
return (
<div className="App">
<CoolBox changeColor={this.changeColor} />
<CoolBox changeColor={this.changeColor} />
<CoolBox changeColor={this.changeColor} />
</div>
);
}
}
Please note
As you can see i bind the handler in the constructor instead of in the render method. that way you only bind it once and not on each render call witch will create a new instance on each render. that is better for performance.
this in a React component does not refer to the DOM element, rather it refers to the Component instance, as the DOM of a given component can change in arbitrary ways as a result of changing state or props.
As mentioned by #Chris in the comments above, you shouldn't really be using jQuery with your React components, unless you have a really good reason, and you know what you're doing.
Instead, you should use Component state to declare what you want, and then reflect your component's state in your render() method.
Here's an example
class CoolBox extends React.Component {
constructor(...args) {
super(...args);
this.state = {
color: 'red'
};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
this.setState({
color: this.state.color === 'red' ? 'green' : 'red'
});
}
render() {
return <div
onClick={this.changeColor}
className="box"
style={{backgroundColor: this.state.color}}
></div>
}
}
class App extends React.Component {
render() {
return (
<div>
<CoolBox />
<CoolBox />
<CoolBox />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
.box {
height: 100px;
width: 100px;
margin: 10px;
display: inline-block;
}
<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>
<div id="app"></div>

When importing styles into a React component, how do I assign a className that updates based off state?

I'm using style-loader to inject css modularly into my components ({style.exampleClassName}).
I want to display a loader for a set amount of time then display an image (at least 16 of these components in a grid pattern).
My current component looks like this:
// Child Component
/**
*
* Sets up props for the child (icon)
*
*/
import React, { Component } from 'react';
import styles from './styles.css';
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden : "shown",
loading: "loading"
};
}
componentDidMount() {
const self = this;
const wait = this.props.wait;
console.log('mounted');
setTimeout(() => {
console.log('timeout working ' + wait);
self.setState({
hidden: "hidden",
loading: "loaded"
});
}, wait);
}
render() {
const hidden = `styles.${this.state.hidden}`;
const loading = `styles.${this.state.loading}`;
return (
<div>
<link rel="stylesheet" type="text/css" href="./app/components/socialgrid/styles.css" />
<div className={this.state.hidden}>
<p>Loading...</p>
</div>
<div className={this.state.loading}>
<p>Child - {this.props.wait}ms</p>
</div>
</div>
)
}
};
export default Child;
// Parent
/**
*
* Individual Icon Component
*
*/
import React, { Component } from 'react';
import cx from 'classnames';
import Img from 'components/Img';
import Child from './Child';
// import Fb from './Fb.png';
class IndIcon extends React.Component{
render() {
return (
<div>
<div>
<Child wait={1000} />
<Child wait={5000} />
<Child wait={4000} />
</div>
</div>
)
}
};
export default IndIcon;
.hidden,
.loading{
display: none;
}
Normally my styles would inject themselves by className={styles.exampleClassName} but here I'm running into the issue of the class not being injected because the class changes based of state (as I said above, just trying different wording to be clear).
I want to assign more than just the display:none element so I do need classes on these components.
Help would be appreciated. Thanks!
You were not too far off... you just weren't passing your const variables into your JSX. Try modifying your render function as follows (amendments highlighted with bold):
render() {
const hidden = `styles.${this.state.hidden}`;
const loading = `styles.${this.state.loading}`;
return (
<div>
<link rel="stylesheet" type="text/css" href="./app/components/socialgrid/styles.css" />
<div className={hidden}>
<p>Loading...</p>
</div>
<div className={loading}>
<p>Child - {this.props.wait}ms
</div>
</div>
)
}
N.B.
Including your styles within the component in this way pollutes the global CSS namespace which can cause problems if you have styles if the same name defined elsewhere in the application ( by yourself or by another developer) which can give rise to unpredictable style behaviour
Even though I really wanted to update those classes on state change, I went this route instead and its way better:
import React, { Component } from 'react';
import Spinner from './Spinner';
import Icon from './Icon';
class Child extends React.Component {
constructor(props){
super(props);
this.state = {
loading: true
}
}
componentDidMount(){
const wait = this.props.wait;
setTimeout(() => {
this.setState({
loading: false
})
}, wait)
}
render() {
let content = this.state.loading ?
<div><Spinner /></div> :
<div><Icon /></div>;
return (
<div>{content}</div>
)
}
};
This way it loads components based off state change and a settimeout.

Categories