Update dynamic created components - javascript

Situation
I have a parent component which renders some child components.
The child component should display information passed from the parent component.
Context
A battleship game with multiplayer:
The parent component contains a field of 10x10 up to 10x30 buttons (child components). If a button is pressed a signal with the position of the button get emitted to the api. The api decides if the pressed button needs to change it color.
Problem
By updating the state of the parent the child components props will not be updated if the child is created dynamically.
Solution Attempts
Dont render Childs dynamically:
Not possible in my case
Use redux
I consider the child as dump/presentational Component because it only displays information. Also in my case there are 100-300 Child Components.
Redux and React
Use refs
There are 100-300 Child Components...Don’t Overuse Refs
Question
What should I do? Is there a solution that is not anti-pattern?
Current Code
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentWillMount(){
this.child=<Child foo={this.state.foo}/>
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
render() {
return (
<div>
Is: {this.child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, 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"></div>
Code with approach from #RaghavGarg and #Clinton Blackburn
class Parent extends React.Component {
constructor(props) {
super(props);
this.state={
foo: 'BAR'
}
this.child=undefined;
}
componentDidMount(){
var that=this
setTimeout(function(){
that.setState({ //it is just here for demonstration
foo: 'FOO'
})
}, 1000);
}
renderChild(){
return <Child foo={this.state.foo} />
}
render() {
const child=this.renderChild()
return (
<div>
Is: {child}
Not Dynamic created: <Child foo={this.state.foo}/>
Actual: <p>{this.state.foo}</p>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<p>{this.props.foo}</p>
);
}
}
ReactDOM.render(<Parent />, 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'/>

Like #Clinton said, componentWillMount would be only called once. So you are essentially initiating your this.child variable, but only updating state, you also need to update the value of the variable.
Since you are just making your dynamic components and saving them in the class instance(like this.child). I would recommend you to do the same task in componentWillUpdate. So that whenever your state changes, it will be reflected in your dependent variables. But please make sure not to use setState inside this lifecycle method.
componentWillUpdate() is invoked just before rendering when new props
or state are being received. Use this as an opportunity to perform
preparation before an update occurs.
Also, consider using constructor to initiate state and not use setState in componentWillMount.
componentWillMount() is invoked just before mounting occurs. It is
called before render(), therefore calling setState() synchronously in
this method will not trigger an extra rendering. Generally, we
recommend using the constructor() instead for initializing state.
Reference:
https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate
https://reactjs.org/docs/react-component.html#unsafe_componentwillmount
Update after adding context
So now, I am supposing you will be having a DS(maybe an object or a nested array) in the state in which you will be maintaining the status of each box(child component). You can loop over it and provide a unique key(maybe like {row}-{col}) to every one of them, now you just need to update the state so to reflect the change in the specific child.
Note: Using the unique key to every child will enable react internally optimize the re-render and will not re-render the child(using unique key), which is not changed. See below code for reference.
this.state = {
boxes: {
1: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
2: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
3: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
},
4: {
1:{ status: true, /* more data */ },
2:{ status: true, /* more data */ },
}
}
};
// this will create a 4x2 box, now you will render using above object
// ...somewhere in your render method
{
Object.keys(this.state.boxes).map(row => (
<div key={`row-${row}`} className="row">
{
Object.keys(this.state.boxes[row]).map(column => (
<Child
key={`box-${row}x${column}`}
data={this.state.boxes[row][column]}
/>
))
}
</div>
))
}
Now, whenever you change the status of say 2x1 box to false, react will only re-render the child with key box-2x1.
Update after OP comment
shouldComponentUpdate should be used to decide whether you want to update the component upon the state changes. It's a lifecycle method of React component. By default it returns true, but you can return false basis on your condition to not update the component. It would definitely help in maintaining the good performance.
Reference: React: Parent component re-renders all children, even those that haven't changed on state change

componentWillMount is only called once when the parent component is inserted into the DOM. It is probably not where you want to create child components that need to be changed after mounting.
See the component lifecycle at https://reactjs.org/docs/react-component.html#the-component-lifecycle.
The simplest solution is to create the child component in the render() method. The child component will be re-created when the parent component is updated.

Related

Using a global JS Object as the state for a React component

We're building a simulation tool and we are trying to replace our current implementation of how our popups are handled using React.
The issue is that the state of our popup component is set to
this.state = connections[this.props.id]
that object is a global object that exists, gets created and update in a separate js file and if I go into the console and change connections[this.props.id].name from "junction 15" to "junction 12", the changes are not rendered immediately. I have to close and reopen the popup so it renders with the correct information.
This is something our architect wants, and the way he explained it was that he needs any changes made to our connections object outside of react NEED to reflected within our popup if it's open, but if the state is set to the marker and I modify the name of the marker in the object through the console, i dont understand why it's not automatically being updated in React
I've looked at trying to use the lifecycle methods, redux, mobx, js proxies, react context but I'm still learning and I think I'm making this more complicated than it should be.
Here's our simple popup with components:
let globalValue = 'initial'
class ReactButton extends React.Component {
constructor(props) {
super(props);
this.state = connections[this.props.id];
this.changeName = this.changeName.bind(this);
}
updateOutsideReactMade() {
this.setState(state);
// this.forceUpdate();
}
changeName(newName) {
connections[this.props.id].name = newName;
this.setState(connections[this.props.id]);
}
// ignore this, this was my attempt at using a lifecycle method
//componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
// if (this.props.name !== prevProps.name) {
// this.setState(this.props.name);
// }
//}
render() {
return (
<div>
<Input onChange={this.changeName} />
<Header name={this.state.name}
id={this.state.id}
/>
</div>
);
}
}
function renderReactButton(iddd, type){
ReactDOM.render(
<ReactButton id={iddd} />,
document.getElementById(`react-component-${type}-${iddd}`)
);
}
class Header extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<h1>{this.props.name}
{this.props.id}</h1>
);
}
}
class Input extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const name = e.target.value;
this.props.onChange(name);
}
render() {
return (
<input onChange={this.handleChange}/>
);
}
}
So my question is how am i able to use an object (connections) that is global as my state for react AND if something modifies the data outside of React that it would be reflected on DOM. Right now, we have it working to where we can change the name through the react popups, but if we change the name through the console it will not update. Thank you guys!
****UPDATE**** 8/15/18
I wrapped each new object as a proxy as it was entered in my array.
connections[key] = new Proxy(polyLine, handleUpdatesMadeToMarkersOutsideOfReact);
I setup a handler:
let handleUpdatesMadeToMarkersOutsideOfReact = {
get: (connections, id) => {
return connections[id];
},
set: (connections, id, value) => {
//trigger react re-render
console.log('inside set');
//trigger react to update
return true;
}
};
Now I'm stuck trying to get the handler to trigger my react component to update. I created a class function for my component that forced the update but I was having a hard time accessing it with the way we have it setup.
Normally state is an object - giving existing object is ok. React requires setState usage to be able to process lifecycle, f.e. render with updated state. Modyfying state object from console doesn't let react to react ;)
You need some kind of observer, sth to tell react than data changed and to force render (call this.forceUpdate()).

Pass value to parent component in React

essentially I have a form component for users to fill out. When they complete an action: onChange={onChange} it returns a value (child component);
function onChange(value) {
console.log("This is: ", value);
}
I want to pass the value to a state in the parent component (so that the from can be processed and form data sent). How can I do this? My constructor looks something like this (parent component);
constructor(props) {
super(props);
this.state = {
form: {
name: '',
age: '',
value: ''
}
};
}
Trying to learn react, please go easy as I'm unsure of how to do this. Would love any feedback! Thanks!
Please read the official documentation start guide carefully and patiently when you are a starter.
In react component, state is the internal state and props is the state passed from outside.
You could only modify the state by setState method.
As for the question you ask, you could define a callback function which call setState in parent component, then pass the callback function as props into the child component.
As Zhili said, you should define a function in your parent component that is passed as prop to the child component.
Here's a brief example:
const Child = ({ onSubmit }) => (
<form onSubmit={onSubmit}>
{ /* your <input/>'s here ... */}
</form>
);
class Parent extends React.Component {
state = {
value: null,
};
onChildSubmit = (value) => {
this.setState({
value,
});
}
render() {
return (
<div class="Something">
<Child onSubmit={} />
</div>
)
}
}

ReactJs - Child not updating after Parent State changed

Im new in ReactJS and I'm having problem with the concept of state and re-render of component.
In my parent component, I have this state in constructor:
this.state = {
locale : 'en',
messages : ENGLISH.messages,
softwareInfo : {
software_name : 'Test Default 1.0',
default_dash : 1,
color_theme: '#009900'
}
}
In the Render, I load a component called DashAppBar passing the softwareInfo:
<DashAppBar softwareInfo = { this.state.softwareInfo} />
In DashAppBar construct, I read that state as property:
this.state = {
software_settings: props.softwareInfo,
[...]
}
And then I print the software name in the bar with this:
const software_name = (
<FormattedMessage
id="software_name"
defaultMessage="{software_name}"
values={{software_name:this.state.software_settings.software_name}}
/>
);
This work fine.
Unfortunally, parent component make an AJAX call that change softwareInfo:
success: function(result){
//window.software_settings = $.parseJSON(result);
//window.software_settings = result;
var settings = {
software_name:result.software_name,
default_dash:result.default_dash,
color_theme:result.color_theme
};
this.setState({softwareInfo : settings});
}
Calling setState should re-render the component but the child value remain the same as original and the new value got from AjaxCall are not displayed.
Where is the error? Change the parent state should update the child.
Thanks!
The issue is that you're passing the parent's state only in the child's constructor. If the parent updates props you either need to do componentWillReceiveProps and update the child state or just use the software_settings as prop directly.
For example, in the child component:
const software_name = (
<FormattedMessage
id="software_name"
defaultMessage="{software_name}"
values={{software_name:this.props.software_settings.software_name}}
/>
);
or with componentWillReceiveProps:
componentWillReceiveProps(nextProps) {
this.setState({ software_settings: nextProps.software_settings});
}
you need to set a componentWillReceiveProps hook on your child component to updated it. https://reactjs.org/docs/react-component.html#componentwillreceiveprops

How to update state of parent component n level above in React

Check the code here
jsfiddle
I wish to update the value property of individual item from the Child component. But as props are immutable and don't trigger re-render the code doesn't work. One way I know to make this work is pass a function from GrandParent to Parent and then to Child and use it to update state of GrandpParent. This will trigger re-render in the Child component. But this also causes re-render of GrandParent, Parent and other siblings of Child component.
// comment
Is there a better way to do this, this doesn't seem optimal to me.
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
this.props.handleIncrement(e.currentTarget.dataset.key)
}
render() {
return (
<div>
<span>{this.props.item.value}</span>
<button data-key={this.props.item.key} onClick={this.handleClick}>inc</button>
</div>
);
}
}
class Parent extends React.Component {
render() {
return (
<div>
{
this.props.list.map((item) => <Child item={item} handleIncrement={this.props.handleIncrement} />)
}
</div>
);
}
}
class GrandParent extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
key: 'one',
value: 1
},
{
key: 'two',
value: 2
},
{
key: 'three',
value: 3
}
]
};
this.handleIncrement = this.handleIncrement.bind(this)
}
handleIncrement(key) {
this.setState({
list: this.state.list.map((l) => {
if (l.key === key) {
return {key: l.key, value: l.value + 1}
}
return l
})
})
}
render() {
return (<Parent list={this.state.list} handleIncrement={this.handleIncrement} />);
}
}
React.render(<GrandParent />, document.getElementById('container'));
You have to pass the handler from the Grand parent and call this handler whenever you wanted to increment. Read about coupling and cohesion for theoretical background.
React is based on the concept of unidirectional data flow. This means that your are passing data down to other components who receive it as props and render it, or passing it down to another sub component.
However, sometimes we want a child component to let a parent component that something happened. To solve this, we use callback. Callbacks are functions that we can pass as props to a child component, so he can use them we something happens. A classic example is to pass an onClick handler to a child component that has a button. Then, when the button is pushed the child component calls it like this:
this.props.onClick()
letting the parent know that the button was clicked. This will work for yor example too. Create a function in the GrandParent component that knows how to increment the value.
incrementValue = (idx) => {
// Copy the list to avoid mutating the state itself.
let newList = this.state.list.slice();
newList[idx].value += 1;
this.setState({list: newList});
}
Then pass this function as callback
<Parent onClick={this.incrementValue}/>
Then bind it to the button click like this:
<button onClick={this.props.onClick}>inc</button>
Read this to learn more about state and props in React.

refs does not work in react in order to call child method

There are two component in react project.
1, Parent
2, Child
Now, I'd like to use childMethod in Parent component.
In some pages of stackoverflow, everyone said refs is effective.
But in my project, it's not working.
class Parent extends Component {
parentMethod(){
this.refs.child.childMethod();
}
render() {
return (
<Child ref='child'/>
);
}
}
class Child extends Component {
childMethod() {
alert('You made it!');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
When I use above code, there is one error code in browser console.
_this3.refs.child.childMethod is not a function
I'd like to use child method, so I have 2 questions.
1, What's "_this3" ? How can I use refs correctly?
2, Do you have any other idea about it?
class Parent extends React.Component {
constructor(props) {
super(props);
// binding methods to the class so they don't lose 'this'
// when invoked from another environment.
this.parentMethod = this.parentMethod.bind(this);
this.setChildRef = this.setChildRef.bind(this);
}
parentMethod() {
this.childNode.childMethod();
}
// intentionally avoided using an arrow fuction inside JSX
// for we don't want a new anonymous fn created on every render.
setChildRef(node) { // receives reference to component as argument
this.childNode = node;
}
render() {
return (
<div>
<Child ref={this.setChildRef}/>
<button onClick={this.parentMethod}>Parent Button - Click me :)</button>
</div>
);
}
}
class Child extends React.Component {
childMethod() {
alert('You made it!');
}
render() {
return (
<h1>Child</h1>
);
}
}
Working fiddle: https://jsfiddle.net/free_soul/9vrLrw8h/
What's "_this3"?
Probably a variable that you may see in browser while debugging. It just represents an execution context.
How can I use refs correctly?
It is preferred to treat the ref as a callback attribute and no longer depend on the refs Object. If you do use the refs Object, avoid
accessing refs of descendant components. You should treat refs as a
private accessor and not part of a component's API. Treat only the
methods exposed on a component instance as its public API.

Categories