Reactjs state changes do not propagate to dynamically-created children components - reactjs

I'm learning react and while working on a bigger project I created this mockup to show the issue I'm having.
The parent component maintains a value in state which it passes to children via props. I want this value to propagate to children and update there when it is changed in the parent state. This works in the first version of this code:
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
}
}
render() {
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
<Child parent_val={this.state.single_val}/>
<Child parent_val={this.state.single_val}/>
<Child parent_val={this.state.single_val}/>
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}
However, in the final version of the project, I need to create the children dynamically. I do it like this:
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
children_divs: [],
}
this.setUp = this.setUp.bind(this);
}
componentDidMount() {
this.setUp();
}
setUp() {
var baseArray = [...Array(3)];
var children = baseArray.map((elem)=>{
return (<Child parent_val={this.state.single_val} />)
});
this.setState({children_divs: children});
}
render() {
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
{this.state.children_divs}
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}
...and the value no longer propagates to children when I press the button and change the parent's state: results screenshots.
How to keep the dynamic creation of child divs and still have the parent value propagate? I sense the issue might me because the value and children divs array are both maintained in the parent state but I'm not sure how to fix it. Hours of searching and looking at examples suggest I should recreate children divs from scratch - run the setUp again - but it seems like an overkill for one state value that I thought should propagate anyway.
Child component code for reference:
import React from "react"
export default function Child(props) {
return (
<div className="Child">
<p>Child val: {props.parent_val.toString()}</p>
</div>
)
}
P.S. I even experimented with adding componentDidUpdate() to children to try and receive props again, but it never triggered.

Ok, so the problem here is your children_divs are created once and value of single_val is added/sent to them at that time (when you have created them in setUp function.
Solution is simple, have your children created in render function, as render is called each time your state changes. This also removes your children_divs from state as its only used to render and serve no other purpose.
import React from "react"
import Child from './Child'
export default class Parent extends React.Component {
constructor() {
super();
this.state = {
single_val: false,
}
}
render() {
var baseArray = [...Array(3)];
var children_divs= baseArray.map((elem)=>{
return (<Child parent_val={this.state.single_val} />)
});
return(
<div className="Parent" style={{border: "solid orange 1px", padding: "15px"}}>
<p>Parent val: {this.state.single_val.toString()}</p>
{children_divs}
<div className="switch"
style={{height: "50px", width: "50px", backgroundColor: "lightPink"}}
onClick={(e)=>{this.setState({single_val: true})}}
>
</div>
</div>
)
}
}

Related

How can I access the props for a nested component in React?

I want to pass props to my nested component but the problem here is the inner card component that is not showing the data passed to it neither the Inner text props nor the inner pct props.
This is my index.js
const tst = () => {
return (
<div className="d-flex">
<Card text={"occupied"} pct={75} width={250}/>
<Card width={370}>
<InnerCard2 Innertext={"Compeleted"} InnerPct={25}/>
<InnerCard2 Innertext={"WIP"} InnerPct={50}/>
<InnerCard2 Innertext={"Upcoming"} InnerPct={25}/>
</Card>
}
This is my card component:
import React from 'react';
import InnerCard2 from './InnerCard2';
class Card extends React.Component {
render() {
var {text="", pct=0, } = this.props
const style = {
width: this.props.width + 'px',
marginRight: '10px'
}
return (
<div className="white-box v-align ht-100" style={style}>
<div className="flex-one"></div>
<div className="wd-80">
<div className="fs-xl fw-bold" style={{ color: '#142654' }}>{pct} %</div>
<div className="small-text">{text}</div>
</div>
</div>
);
}
}
export default Card;
This is my inner card component:
import React from 'react';
class InnerCard2 extends React.Component {
render() {
var {InnerText="", InnerPct=0} = this.props
return (
<div className="wd-80">
<div className="fs-xl fw-bold" style={{ color: '#669900' }}>{InnerPct}%</div>
<div className="small-text">{InnerText}</div>
</div>
);
}
}
export default InnerCard2;
At a glance I'm seeing two problems:
First, InnerCard is expecting a property called InnerText but you're passing it a property called Innertext. Casing matters. Either change the name of the property in the component or change the name of the property being passed to the component.
Second, the Card component is ignoring its children property, so it doesn't render them. By convention any child components wrapped by a component are passed to it as a prop called children. Add children to the prop deconstruction:
var {text="", pct=0, children } = this.props
And display the children wherever you want to in the rendering. For example:
<div>
{children}
</div>

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

In React JS, how do I tell a parent component that something has happened in the child?

I have a React JS app with a simple hierarchy: ContainingBox wraps two InfoBox components. in this example, I simply want to tell the ContainingBox component 1) that something has been clicked, and 2) which InfoBox (by label name) has been clicked?
Here is some basic code that works in my browser to get this question up & running. All it does it console.log when you click onto one of the InfoBox elements on the page.
Essentially, what I am trying to achieve is that I want the ContainingBox to change state (specifically, border color as rendered) when one of the child InfoBox elements is clicked.
I'm not sure what the right direction here is.
I built this app with React 16.10.2, but I would be happy to read answers pointing me towards the latest 'React way' of thinking.
import React from 'react';
import styled from 'styled-components'
import './App.css';
const StyledInfoBox = styled.div`
width: 100px;
border: solid 1px green;
padding: 10px;
cursor: pointer;
`
class InfoBox extends React.Component {
constructor({blurb}) {
super()
this.state = {
label: (blurb ? blurb.label : ""),
}
this.selectBox = this.selectBox.bind(this);
}
selectBox(e) {
e.preventDefault()
console.log("selectBox")
// how do I tell the ContainingBox component 1) that something has been clicked,
// and 2) which InfoBox (by label name) has been clicked?
}
render() {
const {label} = this.state
return (
<StyledInfoBox onClick={this.selectBox} >
{label}
</StyledInfoBox>
)
}
}
class ContainingBox extends React.Component {
render() {
return (
<div>
<InfoBox key={1} blurb={{label: "Aenean malesuada lorem"}} />
<InfoBox key={2} blurb={{label: "Lorem Ipsum dor ameet"}} />
</div>
)
}
}
function App() {
return (
<div className="App">
<ContainingBox />
</div>
)
}
export default App;
You pass a callback from the parent component to child component via the props.
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
changeNameTo = (newName) => this.setState({name: newName})
render() {
return (
<div>
<h1>{this.state.name}</h1>
<p>
<Child callbackExample={this.changeNameTo} />
</p>
</div>
);
}
}
Then you have your Child component.
class Child extends Component {
render() {
return(
<div>
<button onClick={() => this.props.callbackExample("Doggos")}>
Click me
</button>
</div>)
}
}
When you click the button, the callback is invoked setting the state of the parent, which is then reflected when the parent re-renders.

How to call a component function on other component, but from the other component ? React

Currentl, I am doing a React project. I want to ask:
lets say there are 3 components, A,B, and C.
A is my container, B is the input one, and C is the output one.
this is my component A :
import React, { Component } from 'react';
import './App.css';
import {SubmitComponent} from './submitComponent.js'
// import {OutputCo} from './TopicsContainer.js'
const containerStyle = {
border: '2px solid black',
width: '70%',
height: 'auto',
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: '100px',
};
class App extends Component {
render() {
return (
<div style={containerStyle}>
<h1 style={{textAlign:'center'}}>Coding Exercise</h1>
<hr />
<SubmitComponent />
<OutputComponent />
</div>
);
}
}
export default App
Component B is SubmitComponent, component C is OutputComponent, my B is taking the input and save it as its state :
import React, { Component } from 'react';
import React, { Component } from 'react';
const createstyleouter ={
border : '2px solid #AAA',
width : '98%',
height: 'auto',
marginLeft: 'auto',
marginRight: 'auto',
marginTop:'35px',
marginBottom:'50px',
};
const createstyleinner ={
// border: '2px solid blue',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: '10px',
width: '98%',
}
export class SubmitComponent extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
desc: ''
}
this.newTitle = this.newTitle.bind(this);
this.newDesc = this.newDesc.bind(this);
}
newTitle(e) {
this.setState({
title: e.target.value
});
}
newDesc(e) {
this.setState({
desc: e.target.value
});
}
render() {
return (
<div style={createstyleouter}>
<div style={createstyleinner}>
<p><strong>Title:</strong></p>
<textarea style={{width:'100%', height:'20', fontSize:'17px'}} onChange={this.newTitle} maxLength='150' value={this.state.title} placeholder="Enter your topic's title"></textarea>
<p>Description:</p>
<textarea style={{width:'100%', height:'70', fontSize:'17px'}} onChange={this.newDesc} maxLength='150' value={this.state.desc} placeholder="Enter your topic's description'"></textarea>
<button style={{padding: '10px', marginBottom:'10px'}}>Submit</button>
</div>
</div>
);
}
}
I literally did not have any idea how to send this state to C so C can post is as the topic,
Please help
If you wish to call method from other components, you have to pass method around as props.
class Parent extends Component {
render() {
return (
// pass method as props to Child component
<Child parentMethod={this.parentMethod}/>
);
}
parentMethod() {
console.log('Hello World');
}
}
class Child extends Component {
render() {
return (
<button onClick={this.handleClick}>Fire parent method</button>
);
}
handleClick() {
// parent method passed to child is now available as props
// you can call it now & even pass arguments if you like
this.props.parentMethod('foo', 'bar', 2, ['foo', 'bar'], {foo: 'bar'});
}
}
If passing around methods gets tricky (e.g one component wants to call a method of component that is in a totally different place in your component tree), you could consider context.
But be warned - read the docs I linked, it makes your code harder to understand and get into.
In the constructor of the parent:
this.childElement = React.createRef();
In your child element in the parent:
<Child ref = {this.childElement}>
To call a method of the child use:
this.childElement.current.aFunctionInChildClass();
For more information in react documentation/refs and the dom
You need to lift the state up so state is passed as props from container to child components.
Let us say you have a structure
class A
{
getResponse:function(res)
{
set you state//
this.setState({
state:res
})
}
<B state={this.state} callback={this.getResponse}/>
<C state={this.state} />//state is the updated state by function callback
}
in Component B you need to call function callback as
class B
{
callAComponent:function(res)
{
this.props.callback(res)
}
render{
return(
<input type="text" onChange={this.callAComponent}//whaever event you are performing
)
}
}
Now send the update state in C component

React props.children width

Is there anyway of getting the width of React component children. I have wrapper component called for lack of name Container and I add children of div type from Component1 to it. See below example.
I'm wondering if there is a way to get the width of each div child in Container when it mounts.
UPDATED NOTE:
The reason I'm trying to get the containers children widths is so I can dynamical set the containers width based on the total number of children. By setting the containers width to the number of children's width then I can allow for some horizontal scrolling effects I want to do.
Component 1
export default class Component1 extends Component{
render(){
return(
<Container>
<div className="large-box"/>
<div className="large-box-dark"/>
</Container>
)
}
}
Now my Container component.
export default class Container extends Component{
componentDidMount(){
this.props.children.forEach(( el ) => {
// get each child's width
console.log("el =", el);
});
}
render() {
return (
<div className="scroller" ref={(scroller) => { this.scroller = scroller }}>
{this.props.children}
</div>
)
}
}
You can use refs. Although you should avoid touching the DOM until and unless there is no other way. But here we go.
Give your child components a ref which is escape hatch provided by React to access the DOM(with a warning to use other methods before coming to this).
Child Component
class ChildComp extends Component {
getWidth = () => {
//Access the node here and get the width
return this.childNode.offsetWidth(); //or clientWidth();
}
render() {
return (
<div ref={(r) => {this.childNode = r}}>
</div>
);
}
}
Parent Component:
class ParentComp extends Component {
componentDidMount() {
//Access the child component function from here
this.childComponent.getWidth();
}
render() {
return (
<ChildComp
ref={(r) => {this.childComponent = r}} />
);
}
}
But do remember use the above method when there is no way of getting the thing done declaratively.
I would say that technically it doesn't seem possible. The JSX
<div className="large-box"/>
does not refer to DOM element, (which has a width once it's been rendered) but to a React element, which is an in-memory object describing how to make an DOM element. Since the React element isn't rendered or even connected to the actual DOM in the browser, it can't know the width.
Remember that React can be rendered on the server -- there's no way the server can know what the browser on different computer is going to display.
I'd also echo what Pedro Nascimento noted -- this solution is probably best solved some other way, but without context, it's difficult to help.
then try to get the ref of "DivColorOpacy"! and put whatever you want on parent and cutom your behavior. That's silly but that do the job.
with this css
.DivColorOpacy{
height: max-content;
width: max-content;
position: relative;
}
import { Component } from "react";
import * as React from "react";
interface DivColorOpacyProps {
backgroundColor: string,
opacity: number,
}
export class DivColorOpacy extends Component<DivColorOpacyProps, any>{
componentDidMount(){
}
render() {
const { backgroundColor, opacity } = this.props;
return <div className="DivColorOpacy">
{this.props.children}
<div style={{
position: "absolute",
zIndex: -1,
backgroundColor,
opacity,
width: "100%",
height: "100%",
top:0
}} />
</div>
}
}

Resources