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

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>

Related

ReactJS Change Sibling State via Parent

My React structure is
- App
|--SelectStudy
|--ParticipantsTable
In SelectStudy there is a button whose click triggers a message to its sibling, ParticipantsTable, via the App parent. The first Child->Parent transfer works. But how do I implement the second Parent->Child transfer? See questions in comments.
App
class App extends Component {
myCallback(dataFromChild) {
// This callback receives changes from SelectStudy Child Component's button click
// THIS WORKS
alert('SelectStudy Component sent value to Parent (App): ' + dataFromChild.label + " -> " + dataFromChild.value);
// QUESTION: How to Update State of ParticipantsTable (SelectStudy's Sibling) next?
// ........................................................
}
render() {
return (
<div className="App">
<SelectStudy callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable></ParticipantsTable>
</div>
);
}
SelectStudy
class SelectStudy extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
this.handleButtonClick = this.handleButtonClick.bind(this);
}
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.state;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
handleButtonClick = () => {
this.props.callbackFromParent(this.state.selectedStudy);
}
}
ParticipantsTable - this needs to receive a certain variable, e.g. study in its State
class ParticipantsTable extends React.Component {
constructor(props) {
//alert('Constructor');
super(props);
// Initial Definition of this component's state
this.state = {
study: null,
items: [],
error: null
};
}
// THIS METHOD IS AVAILABLE, BUT HOW TO CALL IT FROM App's myCallback(dataFromChild)?
setStudy = (selectedStudy) => {
this.setState({study: selectedStudy});
}
render() {
return ( <div>{this.state.study}</div> );
}
}
The state should live definitively at the App level, not in the child. State needs to live one level above the lowest common denominator that needs access to it. So if both SelectStudy and ParticipantsTable need access to the same bit of state data, then it must live in their closest common ancestor (or above).
This is a core concept of React, known as "lifting state up", so much so that it has its own page in the official React documentation.
In your case, it would look something like this. Notice how state lives in only one place, at the <App /> level, and is passed to children via props.
import React from 'react';
class App extends React.Component {
// State lives here at the closest common ancestor of children that need it
state = {
error: null,
isLoaded: false,
items: [],
selectedStudy: null,
isButtonLoading: false
};
myCallback = (dataFromChild) => {
this.setState(dataFromChild);
};
render() {
return (
<div className="App">
{/* State is passed into child components here, as props */}
<SelectStudy data={this.state} callbackFromParent={this.myCallback}></SelectStudy>
<ParticipantsTable study={this.state.selectedStudy} />
</div>
);
}
}
class SelectStudy extends React.Component {
handleButtonClick = () => {
// Here we execute a callback, provided by <App />, to update state one level up
this.props.callbackFromParent({ ...this.props.selectedStudy, isButtonLoading: true });
};
render() {
const { error, isLoaded, items, itemsForReactSelect, selectedStudy, isButtonLoading } = this.props.data;
return <Button onClick={this.handleButtonClick}>Search</Button>;
}
}
// This component doesn't need to track any internal state - it only renders what is given via props
class ParticipantsTable extends React.Component {
render() {
return <div>{this.props.study}</div>;
}
}
I think what you need to understand is the difference between state and props.
state is internal to a component while props are passed down from parents to children
Here is a in-depth answer
So you want to set a state in the parent that you can pass as props to children
1 set state in the parent
this.state = {
value: null
}
myCallback(dataFromChild) {
this.setState({value: dataFromChild.value})
}
2 pass it as a prop to the children
class ParticipantsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
study: props.study,
items: [],
error: null
};
}
Also, although not related to your question, if you learning React I suggest moving away from class-based components in favour of hooks and functional components as they have become more widely used and popular recently.

How to get the DOM node from a Class Component ref with the React.createRef() API

I have these two components:
import { findDOMNode } from 'react-dom';
class Items extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.selectedItemRef = React.createRef();
}
componentDidMount() {
if (this.props.selectedItem) {
this.scrollToItem();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.selectedItem !== nextProps.selectedItem) {
this.scrollToItem();
}
}
scrollToItem() {
const itemsRef = this.ref.current;
const itemRef = findDOMNode(this.selectedItemRef.current);
// Do scroll stuff here
}
render() {
return (
<div ref={this.ref}>
{this.props.items.map((item, index) => {
const itemProps = {
onClick: () => this.props.setSelectedItem(item.id)
};
if (item.id === this.props.selectedItem) {
itemProps.ref = this.selectedItemRef;
}
return <Item {...itemProps} />;
})}
</div>
);
}
}
Items.propTypes = {
items: PropTypes.array,
selectedItem: PropTypes.number,
setSelectedItem: PropTypes.func
};
and
class Item extends Component {
render() {
return (
<div onClick={() => this.props.onClick()}>item</div>
);
}
}
Item.propTypes = {
onClick: PropTypes.func
};
What is the proper way to get the DOM node of this.selectedItemRef in Items::scrollToItem()?
The React docs discourage the use of findDOMNode(), but is there any other way? Should I create the ref in Item instead? If so, how do I access the ref in Items::componentDidMount()?
Thanks
I think what you want is current e.g. this.selectedItemRef.current
It's documented on an example on this page:
https://reactjs.org/docs/refs-and-the-dom.html
And just to be safe I also tried it out on a js fiddle and it works as expected! https://jsfiddle.net/n5u2wwjg/195724/
If you want to get the DOM node for a React Component I think the preferred way of dealing with this is to get the child component to do the heavy lifting. So if you want to call focus on an input inside a component, for example, you’d get the component to set up the ref and call the method on the component, eg
this.myComponentRef.focusInput()
and then the componentRef would have a method called focusInput that then calls focus on the input.
If you don't want to do this then you can hack around using findDOMNode and I suppose that's why it's discouraged!
(Edited because I realized after answering you already knew about current and wanted to know about react components. Super sorry about that!)

Do react render props cause remounting of the child components?

I was just wondering if people know if using the "render props" pattern causes excessive mounting/unmounting of the child component.
For example, adapting from the react docs (https://reactjs.org/docs/render-props.html):
<Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mounting!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
I know the react docs say:
Using a render prop can negate the advantage that comes from using React.PureComponent if you create the function inside a render method. This is because the shallow prop comparison will always return false for new props, and each render in this case will generate a new value for the render prop.
But, will "mounting!" be called over and over as the user moves the mouse around?
Thanks!
I went ahead and tried to answer my own question using a fiddle. It appears that "mounting!" is not called over and over again:
https://jsfiddle.net/69z2wepo/186690/
Here is the code:
class Hello extends React.Component {
render() {
return <Mouse>
{mouse => (
<ShowMousePosition mouse={mouse}/>
)}
</Mouse>
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: 800, width: 800 }} onMouseMove={this.handleMouseMove}>
{/*
Instead of providing a static representation of what <Mouse> renders,
use the `render` prop to dynamically determine what to render.
*/}
{this.props.children(this.state)}
</div>
);
}
}
class ShowMousePosition extends React.Component {
componentDidMount(){
console.log('mountin!')
}
render () {
const {mouse} = this.props
return (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
componentDidMount is only called once but componentDidUpdate will be called multiple times along with your render function every time your state/props is changed.

componentWillReceiveProps not firing even props value updated

There is simple scenario I updated a value in parent which passed to child component and expected cWRP method firing but not. here code below;
Parent component:
class App extends Component {
changeProps(){//interpreter jumps here fine..
debugger
this.appState.index=15 //update props value
}
render() {
return (
<div className="App">
<EasyABC parentUpdateProps={this.changeProps} appState={this.props.appState} />
</div>
)
}
}
child component:
#observer
export default class EasyABC extends Component{
constructor(props){
super(props)
}
componentWillReceiveProps(nextProps){//why its not jump here after update props in parent?
debugger
}
playSound(){// when this method called, cWRP above should be invoked rigth?
this.props.parentUpdateProps()
}
render(){
return(
<div>
<a onClick={()=> this.playSound()}>Play Sound Again</a>
Edited: i am using mobx as state handler, but dont bother with it
You need to update the state of the component using setState and use the same for passing it to child component
class App extends Component {
constructor() {
this.state = {
index: 0,
};
this.changeProps = this.changeProps.bind(this);
}
changeProps(){
this.setState({
index: 15,
});
// this will update state (not props)
}
render() {
return (
<div className="App">
<EasyABC
parentUpdateProps={this.changeProps}
appState={...this.state}
/>
</div>
);
}
}
<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>
You are updating the state wrongly. You have to use setState e.g.
changeProps() {
this.setState({
index: 15
});
}
You have to dereference the observable value in the render function or it will not fire the will receive props because the component is not actually using it to render.
You could just do something like this:
render() {
if (this.props.appState.index) {
return <div>Play Sound Again</div>;
}
return <div>Play Sound</div>;
}
It really doesn't matter how you use it, but that you access it within the call stack of the render method.

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