setState does not cause children to update - reactjs

I'm building a fairly simple application with react, socket.io, and react-apexcharts (or any other charting library for that matter).
I'm pretty sure i understood the concept of states well enough - I'm not new to programming, but, i can't seem to understand what the problem is.
I have a simple react component:
export default class NumberCandleStickChart extends Component {
constructor(props) {
super(props);
this.state = {
options: {
chart: {
id: "number-candlestick",
},
},
series: props.series,
a: props.a
};
}
render(){
return (
<>
<p>{JSON.stringify(this.state.a)}</p>
<Chart
options={this.state.options}
series={this.state.series}
type="candlestick"
width="500"
/>
</>
);
}
}
And it is used like that:
//imports
class App extends Component {
constructor(props) {
super(props);
this.socket = openSocket('http://localhost:4200');
this.state = {
connected: false,
interval: '10s',
series: [{
name: "numbers",
data: []
}]
}
this.handleNumberIn = this.handleNumberIn.bind(this);
this.socket.on('newNumber', this.handleNumberIn)
}
handleNumberIn(input) {
const newSeries = [...this.state.series];
input.data.map(//map to correct format and push to newSeries);
this.setState({
series: newSeries
});
}
render() {
return (
<div className="App">
<header className="App-header">
<p>
{this.state.connected.toString()}
</p>
<NumberCandleStickChart series={this.state.series} a={this.state.series} />
</header>
</div>
);
}
}
Now, a prop is there to let me see if the state actually changes, and it does, it adds data to the array correctly, according to the docs.
I tried emulating the same with setInterval, and the same happens, and i tried a different charts library, and still no luck - so i am assuming i am doing something not right.
App was created using the create-react-app boilerplate - nothing more.
Any help is appreciated!

Your problem is in handleNumberIn. It should be:
handleNumberIn(input) {
this.setState(prevState => ({
series: [...prevState.series, input.data.map(/* your map function */)],
}));
}
Specifically, Array.prototype.map() returns a new array with the mapped data, it does not modify the existing one.

In the constructor method of NumberCandleStickChart , the lines
series: props.series
a: props.a
are invoked only once.Whenever the state in App.js updates ,the new props received by NumberCandleStickChart are not mapped to its state again.
You do not need the this.state.series in NumberCandleStickChart.You could directly use
<Chart
options={this.state.options}
series={this.props.series}
type="candlestick"
width="500"
/>
You could do the same for this.state.a

Related

Passing functions to child components in React - this.props.handleChange is not a function?

Trying to create a simple todo list and I figure out how to pass the function from the parent component down to the child component without it throwing an error
App Component
class App extends React.Component {
state = {
todo: todoData.map(todo => {
return <TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
}),
count: 0,
}
handleChange = (id) => {
console.log(id)
}
render(){
return(
<div className="flex">
<Header />
<div className="todoList" >
{this.state.todo}
</div>
</div>
)
}
}
TodoItem component
class TodoItem extends React.Component {
render(){
console.log(this.props)
return(
<p className="todoItem" onClick={this.props.clickeds}>
<input type="checkbox" checked={this.props.todo.completed} onChange={() => this.props.handleChange(this.props.todo.id)} />
{this.props.todo.text}
</p>
)
}
}
I'm trying to mess with the onChange handler in the TodoItem component, but I keep getting the same error that this.props.handleChange is not a function
Todo just for reference
todoData = {
id: 2,
text: "Grocery Shopping",
completed: false
},
What am I doing wrong?
When I change the handleChange function to NOT an arrow function in the app component, it works. (handleChange(id)). If I change this function to an arrow function (handleChange = (id) => { } ) I run into this error.
I recommend you use ES6's class syntax for now (which I have added below) as this is how most React tutorials —including the official ones— are written.
The data structure you should keep in the state should be an Array of Todo Objects.
You don't need to keep the components inside the state, simply iterate them on render (and don't worry about its performance, React won't recreate the HTML dom by doing this, so the render will be very efficient).
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos : todoData,
count : 0,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
const todos = this.state.todos.map(todo => (
(todo.id === id)
? Object.assign(todo, { completed: !todo.completed }
: todo
));
this.setState({ todos });
}
render() {
// ...
this.state.todos.map(todo => (
<TodoItem handleChange={this.handleChange} todo={todo} key={todo.id} />
);
}
}
// todoData should probably be an array in case you want to include more by default:
todoData = [
{
id: 2,
text: "Grocery Shopping",
completed: false
},
{
id: 2,
text: "Grocery Shopping",
completed: false
}
];
Be careful about mutating the state
The reason for the ugly and confusing map() and Object.assign() inside handleChange is because you cannot do a shallow copy, nor edit the array directly:
this.state.todos[i].completed = true // ! mutates the state, no good
Preferably Lodash's library or even better, an immutable library or memoized selectors would do the trick of setting a todo as completed in a much nicer fashion.
Without deep cloning or immutability, you would need to copy the object, clone it, then create a new array with the new object, and assign that array to the state.

Debugging es6 in create-react-app

I am trying to debug create-react-app and when I put a breakpoint on an arrow function I have invalid value inside of this keyword and completely strange behavior of stopping breakpoints (the devtools don't allow to put a breakpoint at a valid js expression, it looks like disabled for a breakpoint. I checked on both FF and Chrome browsers. However, when I change arrow function (()=>{}) to the function(){} declaration, the debugging behavior is correct. Does anyone know what the issue is and what react start up project would you recommend where arrow functions are debugged correctly?
My code in App.js looks like here. Try to put a breakpoint inside of the line:
this.setState({value: this.state.value + 1})
this should be App but it's not the case. It is undefined at this particular breakpoint, although the app's behavior is correct. I suspect something is broken with sourcemaps... What are react projects out there with good setup with webpack that handles sourcemaps correctly?
Without using something like let that = this you can use functions for callbacks in JSX properties in a couple of different ways.
If you want to use it directly as an anonymous function you can use like that:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0,
};
}
render() {
return (
<button onClick={(function () {
this.setState(prevState => ({ value: prevState.value + 1 }));
}).bind(this)}>
Click me
</button>
);
}
}
You are binding the function to this here directly. I haven't seen any example of that. Generally people use something like this:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({ value: prevState.value + 1 }));
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
Here we are using our callback function as a reference and binding it in out constructor.
Another alternative is using an arrow function. For this situation you don't need to bind your function. Also, we are using class-fields here, hence no need to use constructor at all.
class App extends React.Component {
state = { value: 0 };
handleClick = () => {
this.setState(prevState => ({ value: prevState.value + 1 }));
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
In a callback for JSX props this' scope changes, it does not refer to the class anymore. So either you will bind it to this or use an arrow function which does not change the scope of this.
Sometimes debug tools can struggle to correctly place breakpoints for lambda functions in these cases. Perhaps you could achieve the same effect by temporarily adding debugger to your source code as follows, to force the breakpoint to hit:
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
value: 0,
};
}
render() {
return (
<div className="App">
<header className="App-header" onClick={() => {
debugger; // ADD THIS: It will act as a breakpoint and force the developer-tools to break so you can step through the code
this.setState({value: this.state.value + 1})
}}>
<img src={logo} className="App-logo" alt="logo"/>
<h1 className="App-title">Welcome to React, the state is {this.state.value}</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>);
}
}
export default App;

Using ImmutableJS and React setState

I'm currently experiencing problems with getting ImmutableJS and React to work properly. This is not using Redux, therefore I am updating state within the component.
The problem is after I update state, my getter properties are missing on the next rerender causing an Error. Why does setState strip these methods away?
Am I not using ImmutableJS correctly? Is ImmutableJS only intended to be with Redux?
const AssociateRoleLocationStateFactory = RecordFactory<AssociateRoleLocationState>({
isEditing: false
})
export default class IAssociateRoleLocation extends React.Component {
constructor(props) {
super(props)
this.state = new AssociateRoleLocationStateFactory({
isEditing: false
})
// `this.state` has `.get` method to retrieve properties
console.log(`initial state:`, this.state) // Record {_map: Map}
}
toggleEditing() {
let nextState = this.state.set('isEditing', !this.state.get('isEditing'))
// `nextState` has `.get` method to retrieve properties
console.log(`next state:`, nextState) // Record {_map: Map}
this.setState(nextState)
}
render() {
console.log(this.state) // {_map: Map} // Its missing `.get` method on second render
let isEditing = this.state.get('isEditing')
return (
<div className="site-single-column">
{isEditing ? (
<div>
Is Editing!!
</div>
) : (
<button style={{ alignSelf: 'center' }} onClick={this.toggleEditing}>
Toggle
</button>
)}
</div>
)
}
}
Currently, React only supports plain js object as the state. You can wrap whatever you want in another key/value layer like
constructor(props) {
super(props);
this.state = {
payload: new AssociateRoleLocationStateFactory({
isEditing: false
})
}
}
There are discussions around this going on like this: https://github.com/facebook/react/issues/3303
But before that, stay pure.

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<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" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

How to pass props to recursive child component and preserve all props

I've run into an interesting problem. I have a parent component that has an array of objects that gets passed to a child component that is a TreeView, meaning it is recursive. I'm passing a function, and a couple of other props to the child, along with the array of objects that is handled recursively by the child. When logging the props in the render function of the child, on the first render all the props are there, but as the recursive function moves through each object in the array, it 'loses' all the other props that are not being handled recursively.
When the component first renders the props object is: prop1, prop2, arrayOfObjects
As it re-renders as recursion is happening, the props object in the child becomes: arrayOfObjects.
prop1, and prop2 have disappeared.
The end result is that I'm not able to call a function in the parent from the child, so I cannot update the state depending on which node in the tree is clicked. I'm not using redux, because this is a style guide - separate from our production app, that is meant to be for devs only, and simple so if possible I'd like to handle all the state from within the components.
There is one other issue - The array of objects is the folder structure of files in our styleguide, and I need to be able to click on a name in the list, and update the view with the contents of that file. This works fine when the file does not have any children, but when there are child nodes, if I click on the parent, the child is clicked. I've tried e.stopPropagation(), e.preventDefault() etc. but have not had any luck. Thanks in advance.
Parent:
import React, {Component} from 'react'
import StyleGuideStructure from '../../styleguide_structure.json'
import StyleTree from './style_tree'
class StyleGuide extends Component {
constructor(props) {
super(props)
let tree = StyleGuideStructure
this.state = {
tree: tree
}
This is the function I'd like to call from the child
setVisibleSection(nodeTitle) {
this.setState({
section: nodeTitle
})
}
render() {
return(
<TreeNode
className="class-name-here"
setVisibleSection={this.setVisibleSection.bind(this)}
node={this.state.tree}
/>
)
}
}
export default StyleGuide
This is essentially what I have in the child, as a fiddle here:
https://jsfiddle.net/ssorallen/XX8mw/
The only difference is that inside the toggle function, I'm trying to call setVisibleSection in the parent, but no dice.
Here is a photo of the console showing the props when the component initially renders, and then after recursion:
I don't think I really understand your 2nd issue. Could you post a fiddle showing the problem?
I think your first issue is that you need to pass the props down to the children. I tried to transcribe your example to your fiddle. You can see by clicking the nodes, the title switched to the node's name.
https://jsfiddle.net/hbjjq3zj/
/**
* Using React 15.3.0
*
* - 2016-08-12: Update to React 15.3.0, class syntax
* - 2016-02-16: Update to React 0.14.7, ReactDOM, Babel
* - 2015-04-28: Update to React 0.13.6
*/
class TreeNode extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
this.props.setVisibleSection(this.props.node.title)
};
render() {
var childNodes;
var classObj;
if (this.props.node.childNodes != null) {
childNodes = this.props.node.childNodes.map((node, index) => {
return <li key={index}><TreeNode {...this.props} node={node} /></li>
});
classObj = {
togglable: true,
"togglable-down": this.state.visible,
"togglable-up": !this.state.visible
};
}
var style;
if (!this.state.visible) {
style = {display: "none"};
}
return (
<div>
<h5 onClick={this.toggle} className={classNames(classObj)}>
{this.props.node.title}
</h5>
<ul style={style}>
{childNodes}
</ul>
</div>
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: true,
};
}
toggle = () => {
this.setState({visible: !this.state.visible});
};
setVisibleSection(nodeTitle) {
this.setState({
title: nodeTitle
})
}
render() {
return (
<div>
Title: {this.state.title}
<TreeNode
node={tree}
setVisibleSection={this.setVisibleSection.bind(this)}
/>
</div>
);
}
}
var tree = {
title: "howdy",
childNodes: [
{title: "bobby"},
{title: "suzie", childNodes: [
{title: "puppy", childNodes: [
{title: "dog house"}
]},
{title: "cherry tree"}
]}
]
};
ReactDOM.render(
<ParentComponent />,
document.getElementById("tree")
);
<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>

Resources