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

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>

Related

How do I limit the user to only selecting one component?

I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}
You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.
Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}

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.

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>

Communication between two child components without causing a complete re-render in parent

I've been learning and using ReactJS. After reading numerous threads and blog posts still I'm confused with having effective communication between components in ReactJS.
Let's say I've a parent component "A" and it has numerous child components "a", "b", "c", "d", "e" etc. Now something happened in the "a" component and I want to re-render the "b" component.
Since "a" and "b" has the same parent I can use the parent itself as the communication medium between this two. But the problem here is when I use parent as the communication medium it changes it's state and trigger the re-render and that'll cause the re-rendering of all the components "a", "c", "d", "e" etc. which I don't like it, instead I want to forcefully re-render only the "b" component.
Is this possible? How can I achieve this? Basically I want to do this for performance reasons.
There is one way to do that, but...
JavaScript if fast and React reconciliation algorithm is also very fast. React will not re-render DOM, it will just check if there are changes in other components and apply if there are.
You can make what you want. For that you have shouldComponentUpdate function, which is called before rendering. So if you implement this function for your child components, they will not be re-rendered. However, it is recommended to use this function only of you really have issues with performance.
If you child components are simple and don't have deep trees of objects, you can use shallow compare helper function. There is a probability that in future it will be included in all pure components by default.
It's totally feasible using plain React. Your parent component need to act as a container and hold the state corresponding to the props of its children. When one of its child triggers an update, if you implement the shouldComponentUpdate method other child will not re-render. Here is a working snippet, if you checked / unchecked one of the item in the first list, all three items will re-render. In the second list bellow, only the targeted item will re-render.
class ItemsList extends React.Component {
constructor(props) {
super(props)
this.state = {
items: {
a: false,
b: false,
c: true
}
}
}
render() {
const self = this
return (
<ul>
{Object.keys(self.state.items).map(
id => (
<Item
key={id}
id={id}
checked={self.state.items[id]}
handleChange={(e) => {
e.persist()
this.setState(state => ({
...self.state,
items: {
...self.state.items,
[id]: e.target.checked
}
}))
}}/>
)
)}
</ul>
)
}
}
class Item extends React.Component {
shouldComponentUpdate(nextProps) {
return nextProps.checked != this.props.checked
}
render() {
console.log(`rendering ${this.props.id}`)
return (
<li>
<span>{this.props.id}</span>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleChange}/>
</li>
)
}
}
class ItemsList2 extends React.Component {
constructor(props) {
super(props)
this.state = {
items: {
a: false,
b: false,
c: true
}
}
}
render() {
const self = this
return (
<ul>
{Object.keys(self.state.items).map(
id => (
<Item2
key={id}
id={id}
checked={self.state.items[id]}
handleChange={(e) => {
e.persist()
this.setState(state => ({
...self.state,
items: {
...self.state.items,
[id]: e.target.checked
}
}))
}}/>
)
)}
</ul>
)
}
}
class Item2 extends React.Component {
render() {
console.log(`rendering ${this.props.id}`)
return (
<li>
<span>{this.props.id}</span>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleChange}/>
</li>
)
}
}
ReactDOM.render(<ItemsList/>, document.getElementById('app2'))
ReactDOM.render(<ItemsList2/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react-dom.js"></script>
<script src="https://wzrd.in/standalone/react-addons-perf#latest"></script>
<div id="app"></div>
<div id="app2"></div>

Getting DOM node from React child element

Using the React.findDOMNode method that was introduced in v0.13.0 I am able to get the DOM node of each child component that was passed into a parent by mapping over this.props.children.
However, if some of the children happen to be React Elements rather than Components (e.g. one of the children is a <div> created via JSX) React throws an invariant violation error.
Is there a way to get the correct DOM node of each child after mount regardless of what class the child is?
this.props.children should either be a ReactElement or an array of ReactElement, but not components.
To get the DOM nodes of the children elements, you need to clone them and assign them a new ref.
render() {
return (
<div>
{React.Children.map(this.props.children, (element, idx) => {
return React.cloneElement(element, { ref: idx });
})}
</div>
);
}
You can then access the child components via this.refs[childIdx], and retrieve their DOM nodes via ReactDOM.findDOMNode(this.refs[childIdx]).
If you want to access any DOM element simply add ref attribute and you can directly access that element.
<input type="text" ref="myinput">
And then you can directly:
componentDidMount: function()
{
this.refs.myinput.select();
},
Their is no need of using ReactDOM.findDOMNode(), if you have added a ref to any element.
This may be possible by using the refs attribute.
In the example of wanting to to reach a <div> what you would want to do is use is <div ref="myExample">. Then you would be able to get that DOM node by using React.findDOMNode(this.refs.myExample).
From there getting the correct DOM node of each child may be as simple as mapping over this.refs.myExample.children(I haven't tested that yet) but you'll at least be able to grab any specific mounted child node by using the ref attribute.
Here's the official react documentation on refs for more info.
You can do this using the new React ref api.
function ChildComponent({ childRef }) {
return <div ref={childRef} />;
}
class Parent extends React.Component {
myRef = React.createRef();
get doSomethingWithChildRef() {
console.log(this.myRef); // Will access child DOM node.
}
render() {
return <ChildComponent childRef={this.myRef} />;
}
}
React.findDOMNode(this.refs.myExample) mentioned in another answer has been deprectaed.
use ReactDOM.findDOMNode from 'react-dom' instead
import ReactDOM from 'react-dom'
let myExample = ReactDOM.findDOMNode(this.refs.myExample)
I found an easy way using the new callback refs. You can just pass a callback as a prop to the child component. Like this:
class Container extends React.Component {
constructor(props) {
super(props)
this.setRef = this.setRef.bind(this)
}
setRef(node) {
this.childRef = node
}
render() {
return <Child setRef={ this.setRef }/>
}
}
const Child = ({ setRef }) => (
<div ref={ setRef }>
</div>
)
Here's an example of doing this with a modal:
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
modalOpen: false
}
this.open = this.open.bind(this)
this.close = this.close.bind(this)
this.setModal = this.setModal.bind(this)
}
open() {
this.setState({ open: true })
}
close(event) {
if (!this.modal.contains(event.target)) {
this.setState({ open: false })
}
}
setModal(node) {
this.modal = node
}
render() {
let { modalOpen } = this.state
return (
<div>
<button onClick={ this.open }>Open</button>
{
modalOpen ? <Modal close={ this.close } setModal={ this.setModal }/> : null
}
</div>
)
}
}
const Modal = ({ close, setModal }) => (
<div className='modal' onClick={ close }>
<div className='modal-window' ref={ setModal }>
</div>
</div>
)

Resources