This is my first week into React. I'm finding the implementation of trickle down state to be elusive.
What I would like to happen: When the parent container receives a message from the socket I want the state change of the parent to trickle down to each of the child elements.
What is currently happening: With the code below all the components render to the screen, the message is received, but the child elements do not register any type of update.
**Update: Everything works fine if I don't loop the ProgressBlocks into an array and then place that array in the render tree with {this.state.blocks}. This is unfortunate since it's not dynamic but at least there's progress.
Parent Container
class InstanceContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
id: ids.pop(),
callInProgress: false,
callsCompleted: 0,
eventProgress: 0,
blocks: []
}
}
componentWillMount() {
const lightColors = ['white', 'white', 'cyan', 'green', 'green', 'yellow', 'orange', 'magenta', 'red']
for (let i = 0; i < 10; i++) {
this.state.blocks.push(<ProgressBlock bgColor={lightColors[i]} eventPlace={i} eventProgress={this.state.eventProgress} />)
}
}
render() {
socket.on(this.state.id, (msg) => {
console.log(msg)
console.log('RECEIVED MESSAGE')
console.log(this.state.id)
if (msg) {
this.setState((prevState) => ({
eventProgress: 0,
callsCompleted: prevState.callsCompleted + 1
}))
} else {
this.setState((prevState) => ({
eventProgress: prevState.eventProgress + 1
}))
}
console.log(this.state.eventProgress)
})
return (
<div className="instance-container" id={this.state.id}>
{this.state.blocks}
<CompletionCounter callsCompleted={this.state.callsCompleted} />
<DisplayLog />
<VenueName />
</div>
)
}
}
Child Element
class ProgressBlock extends React.Component {
constructor(props) {
super(props)
this.state = {
light: false,
eventProgress: this.props.eventProgress,
bgColor: this.props.bgColor
}
}
componentWillUpdate() {
if (this.state.eventProgress >= this.props.eventPlace) {
this.setState({
light: true
})
}
}
render() {
console.log(this.state.eventProgress) // Does not log when parent changed
console.log(this.props.eventPlace) // Does not log when parent changed
const styleObj = {
backgroundColor: '#232323'
}
if (this.light) {
const styleObj = {
backgroundColor: this.props.bgColor
}
}
return <div className="progress-block" style={styleObj} />
}
}
The constructor in the child element is called once and hence your state is not gettinng updated in the child element. Also its an antipattern to set the state at the constructor when it depends on props. You should do it in the componentWillReceiveProps lifecycle function which is called when the props are updated. See the code below
class ProgressBlock extends React.Component {
constructor(props) {
super(props)
this.state = {
light: false,
eventProgress: '',
bgColor: ''
}
}
componentWillMount() {
this.setState({eventProgress: this.props.eventProgress, bgColor: this.props.bgColor});
}
componentWillReceiveProps(nextProps) {
this.setState({eventProgress: nextProps.eventProgress, bgColor: nextProps.bgColor});
}
componentWillUpdate() {
if (this.state.eventProgress >= this.props.eventPlace) {
this.setState({
light: true
})
}
}
render() {
console.log(this.state.eventProgress)
console.log(this.props.eventPlace)
const styleObj = {
backgroundColor: '#232323'
}
if (this.light) {
const styleObj = {
backgroundColor: this.props.bgColor
}
}
return <div className="progress-block" style={styleObj} />
}
}
Related
I'm trying to figure out why the shouldComponentUpdate method doesn't fire.
Here are what my props looks like:
Object
children: [Object, Object, Object, Object, Object] (5)
data: Array (2)
0 {id: 1, activated: true}
1 {id: 2, activated: true}
key: undefined
rowRender: function()
I have the following snippet.
export function withState(WrappedGrid) {
return class StatefulGrid extends React.Component {
constructor(props) {
super(props);
setInterval(() => console.log(this.props), 5000)
}
shouldComponentUpdate(){
console.log("props updated")
return true
}
componentDidUpdate(prevProps, prevState) {
console.log("update")
}
render() { ... }
}
}
Creating StatefulGrid component :
const StatefulGrid = withState(Grid);
class NFTTable extends React.Component {
constructor({rules}){
super()
this.rules = rules
this.renderers = new Renderers()
this.contextMenu = false
}
render(){
return([
<StatefulGrid
key={"table"}
data={this.rules}
rowRender={this.renderers.rowRender}
>
// GridColumn ...
</StatefulGrid>,
<ContextMenu
key={"context_menu"}
caller={this.renderers.caller}
/>
])
}
}
Updating the data :
export default class ContextMenu extends React.Component{
constructor({caller}){
super()
this.state = {
show: false
}
caller(this)
}
handleContextMenu = (e, dataItem) => {
e.preventDefault()
this.dataItem = dataItem
this.offSet = { left: e.clientX, top: e.clientY }
this.activated = dataItem.dataItem.activated
this.setState({ show: true })
}
componentDidMount() {
document.addEventListener('click', () => {
if(this.state.show)
this.setState({ show: false })
})
}
handleSelect = (e) => {
switch(e.item.data){
case "activation":
this.toggleActivation()
break
default:
console.log("Error, non registered event " + e.data)
}
}
toggleActivation(){
this.dataItem.dataItem.activated = !this.dataItem.dataItem.activated;
}
render() {
return (
<Popup show={this.state.show} offset={this.offSet}>
<Menu vertical={true} style={{ display: 'inline-block' }} onSelect={this.handleSelect}>
<MenuItem data="delete" text="Delete rule"/>
<MenuItem data="activation" text={this.activated ? "Deactivate Rule" : "Activate rule"}/>
</Menu>
</Popup>
);
}
}
Calling handleContextMenu :
export default class Renderers {
rowRender = (trElement, dataItem) => {
let greyed = { backgroundColor: "rgb(235,235,235)" }
let white = { backgroundColor: "rgb(255,255,255)" }
const trProps = {
...trElement.props,
style: dataItem.dataItem.activated ? white : greyed,
onContextMenu : (e) => {
this.contextMenu.handleContextMenu(e, dataItem)
}
};
return React.cloneElement(trElement, { ...trProps }, trElement.props.children);
}
caller = (contextMenu) => {
this.contextMenu = contextMenu
}
}
For debugging purpose, I've added a setInterval method. Through another snippet in my code, I do change props.data[0].activated to false and the changes do reflect in the console log. So why is shouldComponentUpdate not triggered and how to get it to trigger ?
In component BlocklyDrawer i'm trying to change the code state of the parent component. I do it in the onChange() event, calling the method of the parent component handleCodex:
constructor(props) {
super(props);
this.state = {
code : "xxx",
};
this.handleCodex =
this.handleCodex.bind(this);
}
handleCodex(codex){
this.setState = ({
code: codex,
});
}
<BlocklyDrawer
tools={[INICIAR, MOVER, ATACAR]}
language = {Blockly.Javascript}
onChange={(code, workspace) => {
this.handleCodex(code);
}}
appearance={
{
categories: {
Agente: {
colour: '160'
},
},
}
}
>
Although the method handleCodex is executed, the code state does not change.
constructor(props) {
super(props);
this.state = {
code : "xxx",
};
this.handleCodex =
this.handleCodex.bind(this);
}
handleCodex(codex){
this.setState({
code: codex,
}); // this.setState is like a function call. Not a assignment statement.
}
<BlocklyDrawer
tools={[INICIAR, MOVER, ATACAR]}
language = {Blockly.Javascript}
onChange={(code, workspace) => {
this.handleCodex(code);
}}
appearance={
{
categories: {
Agente: {
colour: '160'
},
},
}
}
>
I'am getting props from child in getCount function. And set it prop into state. Than i try set it in component and get infinity loop. How can i fix that?
There is code of parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: count
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
child:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.state = { h: 0 };
this.counter = 0;
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
render() {
this.counter++;
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
The problem lies in your Message component.
You are using getCount() inside your componentDidUpdate() method. This causes your parent to re-render, and in turn your Message component to re-render. Each re-render triggers another re-render and the loop never stops.
You probably want to add a check to only run the function if the props have changed. Something like:
componentDidUpdate(prevProps) {
if(prevProps.color !== this.props.color) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
}
This will keep the functionality you need, but prevent, not only the infinity-loop, but also unnecessary updates.
I have the following code in reactjs:
How can I access any component like jquery using ID or there is some other solution?
Progressbar.jsx
class Progressbar extends Component {
constructor() {
super();
this.state = { percentage: 0 };
}
progress(percentage) {
this.setState({ percentage:percentage });
}
render() {
// using this.state.percentage for the width css property
........
........
}
};
FileUploader.jsx
class FileUploader extends Component {
onUploadProgress() {
// I want to call Progress1.progress( percentage_value )
}
render() {
........
<Progressbar id="Progress1" />
........
........
}
};
You are not thinking about the structure correctly. Progressbar does not need to maintain state at all:
class Progressbar extends Component {
render() {
// using this.props.percentage for the width css property
}
};
Keep state in FileUploader and pass it down to progress bar.
class FileUploader extends Component {
constructor() {
super();
this.state = { percentage: 0 };
}
onUploadProgress(percentage) {
this.setState({ percentage: percentage });
}
render() {
...
<Progressbar percentage={this.state.percentage} />
}
};
ProgressBar can be a stateless component.
The value of ProgressBar is updated from the state of its parent.
There is a demo:
const ProgressBar = props => (
<div style={{ display: 'block', width: `${props.percentage}%`, height: 20, backgroundColor: '#ccc' }} />
);
class App extends Component {
state = {
percentage: 0,
}
componentDidMount() {
setInterval(() => {
let nextPercent = this.state.percentage+1;
if (nextPercent >= 100) {
nextPercent = 0;
}
this.setState({ percentage: nextPercent });
}, 100);
}
render() {
return (
<div>
<h2>Progress bar</h2>
<ProgressBar percentage={this.state.percentage} />
</div>
);
}
}
There is the fully functional codesandbox demo https://codesandbox.io/s/6y9p69q0xw
I wish to add the checks done (once the component mounts in CDM) to detect userAgent - for the purposes of mobile/flash/touchDevice detections to context rather than to the state. Is this possible? if so how would you do that? I am currently getting undefined when I attempt to access the value fo the context for the isFlashInstalled. Here is glimpse into the component setting the context:
App.js
export class App extends Component {
static childContextTypes = {
isFlashInstalled: React.PropTypes.bool
};
constructor() {
super();
this.state = {
isFlashInstalled: false
};
}
getChildContext() {
return {
isFlashInstalled: this.state.isFlashInstalled
};
}
componentDidMount() {
const flashVersion = require('../../../client/utils/detectFlash')();
// I know this could be done cleaner, focusing on how for now.
if (flashVersion && flashVersion.major !== 0) {
this.setFlashInstalled(true);
} else {
this.setFlashInstalled(false);
}
}
setFlashInstalled(status) {
this.setState({isFlashInstalled: status});
}
}
Later when trying to access isFlashInstalled from context I will get undefined
ChildComponent.js
export class ChildComponent extends Component {
// all the good stuff before render
render() {
const {isFlashInstalled} = this.context
console.log(isFlashInstalled); // undefined
}
}
did you correctly set up context types for parent and child? I did a test and it works, see the componentDidMount that set the state asynchronously:
class Parent extends React.Component {
state = {
color: 'red'
}
getChildContext() {
return {
color: this.state.color
};
}
componentDidMount() {
setTimeout(() => this.setState({color: 'blue'}), 2000)
}
render() {
return (
<div>Test <Button>Click</Button></div>
);
}
}
Parent.childContextTypes = {
color: React.PropTypes.string
}
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
http://jsbin.com/cogikibifu/1/edit?js,output