How can I pass props/context to dynamic childrens in react? - javascript

I am using react, and I am trying to pass props/context to my dynamic childrens,
by dymamic childrens I mean childrens are render using
{this.props.children}
How can I pass to this children (In my code I know it's type) context/props?
In this jsbin there is an example that it dosen't work on dynamic childrens.
http://jsbin.com/puhilabike/1/edit?html,js,output

Though #WiredPrairie's answer is correct, the React.addons.cloneWithProps is deprecated as of React v0.13RC. The updated way to do this is to use React.cloneElement. An example:
renderedChildren = React.Children.map(this.props.children, function (child) {
return React.cloneElement(child, { parentValue: self.props.parentValue });
});

There's not a a great way to do this that is clear and passing all the properties of the parent isn't a great pattern and could lead to some very difficult to follow code if not done carefully (and with excellent documentation). If you have a subset of properties though, it's straightforward:
JsFiddle
Assuming you're using React with Addons, you can clone the children of a React component and set new property values on them. Here, the code just copies a property called parentValue into each child. It needs to create a clone of each element as the child element had already been created.
var Hello = React.createClass({
render: function() {
var self = this;
var renderedChildren = React.Children.map(this.props.children,
function(child) {
// create a copy that includes addtional property values
// as needed
return React.addons.cloneWithProps(child,
{ parentValue: self.props.parentValue } );
});
return (<div>
{ renderedChildren }
</div>)
;
}
});
var SimpleChild = React.createClass({
render: function() {
return <div>Simple { this.props.id }, from parent={ this.props.parentValue }</div>
}
});
React.render((<Hello parentValue="fromParent">
<SimpleChild id="1" />
<SimpleChild id="2" />
</Hello>), document.body);
Produces:
Simple 1, from parent=fromParent
Simple 2, from parent=fromParent

Spreading props on DOM elements
https://github.com/vasanthk/react-bits/blob/master/anti-patterns/07.spreading-props-dom.md
When we spread props we run into the risk of adding unknown HTML
attributes, which is a bad practice.
const Sample = () => (<Spread flag={true} domProps={{className: "content"}}/>);
const Spread = (props) => (<div {...props.domProps}>Test</div>);

Related

React Hooks (Rendering Arrays) - Parent component holding a reference of children that are mapped vs Parent component holding the state of children

I have been learning hooks in react for the past couple of days, and I tried creating a scenario where I need to render a big grid on screen, and update the background color of the nodes depending on the action I want to take. There are two actions that will change the background color of a node, and these two actions must coexist.
The cursor hovers a node while it is clicked.
There exists an algorithm inside the Grid component that will change backgrounds of some of the
nodes.
The way I see it, there are multiple ways I can achieve this, but I am having some trouble with the way hooks were intended to be used. I will first walk you through my thought process on how this could be achieved from what I learned, and then show you the implementation that I tried. I tried to keep the important parts of the code so it can be understood clearly. Please let me know if I missed somethings or misunderstood a concept completely.
The children can hold their own state and know how to update themselves. The parent can hold the reference to each children of the list, and call the necessary function from the reference of the child when it is needed in order to update the children.
Works well for the first and the second action to be taken. This solution causes no performance issues since the children manage their own state, and if the parent updates the children state via reference, the only child to be re-rendered will be the one that gets called.
This solution is seen as an anti-pattern from what I read.
const Grid = () => {
// grid array contains references to the GridNode's
function handleMouseDown() {
setIsMouseDown(true);
}
function handleMouseUp() {
setIsMouseDown(false);
}
function startAlgorithm() {
// call grid[row][column].current.markAsVisited(); for some of the children in grid.
}
return (
<table>
<tbody>
{
grid.map((row, rowIndex) => {
return (
<tr key={`R${rowIndex}`}>
{
row.map((node, columnIndex) => {
return (
<GridNode
key={`R${rowIndex}C${columnIndex}`}
row={rowIndex}
column={columnIndex}
ref={grid[rowIndex][nodeIndex]}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
/>
);
})
}
</tr>
);
}
)
}
</tbody>
</table>
);
};
const GridNode = forwardRef((props, ref) => {
const [isVisited, setIsVisited] = useState(false);
useImperativeHandle(ref, () => ({
markAsVisited: () => {
setIsVisited(!isVisited);
}
}));
function handleMouseDown(){
setIsVisited(!isVisited);
}
function handleMouseEnter () {
if (props.isMouseDown.current) {
setIsVisited(!isVisited);
}
}
return (
<td id={`R${props.row}C${props.column}`}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
className={classnames("node", {
"node-visited": isVisited
})}
/>
);
});
2. The state of the children could be given as props from the parent, any update operation can be achieved inside the parent. (Children gets updated correctly, render gets called in only the necessary children, but the DOM seems to stutter. If you move the mouse at a certain speed, nothing happens, and every visited node gets updated at once.)
Doesn't work for the first action. Children gets updated correctly, render gets called in only the necessary children, but the DOM seems to stutter. If you move the mouse at a certain speed, nothing happens and every visited node gets updated at once.
const Grid = () => {
// grid contains objects that have boolean "isVisited" as a property.
function handleMouseDown() {
isMouseDown.current = true;
}
function handleMouseUp() {
isMouseDown.current = false;
}
const handleMouseEnterForNodes = useCallback((row, column) => {
if (isMouseDown.current) {
setGrid((grid) => {
const copyGrid = [...grid];
copyGrid[row][column].isVisited = !copyGrid[row][column].isVisited;
return copyGrid;
});
}
}, []);
function startAlgorithm() {
// do something with the grid, update some of the "isVisited" properties.
setGrid(grid);
}
return (
<table>
<tbody>
{
grid.map((row, rowIndex) => {
return (
<tr key={`R${rowIndex}`}>
{
row.map((node, columnIndex) => {
const {isVisited} = node;
return (
<GridNode
key={`R${rowIndex}C${columnIndex}`}
row={rowIndex}
column={columnIndex}
isVisited={isVisited}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnterForNodes}
/>
);
})
}
</tr>
);
}
)
}
</tbody>
</table>
);
};
const GridNode = ({row, column, isVisited, onMouseUp, onMouseDown, onMouseEnter}) => {
return useMemo(() => {
function handleMouseEnter() {
onMouseEnter(props.row, props.column);
}
return (
<td id={`R${row}C${column}`}
onMouseEnter={handleMouseEnter}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
className={classnames("node", {
"node-visited": isVisited
})}
/>
);
}, [props.isVisited]);
}
I have two questions that I want to ask on this topic.
In the first implementation; the parent component doesn't re-render when a node changes its' state. Is it wrong to just utilize this anti-pattern if it is beneficial in this kind of situations?
What may be the cause of the stutter that the second implementation suffers from? I have spent a while reading the docs and trying out different things, but cannot find the reason of the stuttering that is happening.
As you say that using refs to control child data is an anti-pattern, However it doesn't mean that you cannot use it.
What it means is that if there are better and more performant means, its better to use them as they lead to better readability of the code and also improve debugging.
In your case using a ref definitely makes it easier to update state and also prevents a lot of re-rendering is a good way to implement the above solution
What may be the cause of the stutter that the second implementation suffers from? I have spent a while reading the docs and trying out different things, but cannot find the reason of the stuttering that is happening.
A lot of the problem in the second solution arise from the fact that you define functions which are recreated on each re-render and hence cause the entire grid to be re-rendered instead of just the cell. Make use of useCallback to memoize these function in Grid component
Also you should use React.memo instead of useMemo for your usecase in GridNode.
Another thing to note is that you are mutating the state while updating, Instead you should update it in an immutable manner
Working code:
const Grid = () => {
const [grid, setGrid] = useState(getInitialGrid(10, 10));
const isMouseDown = useRef(false);
const handleMouseDown = useCallback(() => {
isMouseDown.current = true;
}, []);
const handleMouseUp = useCallback(() => {
isMouseDown.current = false;
}, []);
const handleMouseEnterForNodes = useCallback((row, column) => {
if (isMouseDown.current) {
setGrid(grid => {
return grid.map((r, i) =>
r.map((c, ci) => {
if (i === row && ci === column)
return {
isVisited: !c.isVisited
};
return c;
})
);
});
}
}, []);
function startAlgorithm() {
// do something with the grid, update some of the "isVisited" properties.
setGrid(grid);
}
return (
<table>
<tbody>
{grid.map((row, rowIndex) => {
return (
<tr key={`R${rowIndex}`}>
{row.map((node, columnIndex) => {
const { isVisited } = node;
if (isVisited === true) console.log(rowIndex, columnIndex);
return (
<GridNode
key={`R${rowIndex}C${columnIndex}`}
row={rowIndex}
column={columnIndex}
isVisited={isVisited}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnterForNodes}
/>
);
})}
</tr>
);
})}
</tbody>
</table>
);
};
const GridNode = ({
row,
column,
isVisited,
onMouseUp,
onMouseDown,
onMouseEnter
}) => {
function handleMouseEnter() {
onMouseEnter(row, column);
}
const nodeVisited = isVisited ? "node-visited" : "";
return (
<td
id={`R${row}C${column}`}
onMouseEnter={handleMouseEnter}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
className={`node ${nodeVisited}`}
/>
);
};
P.S. While useCallback and other memoizations will help give to some performance benefits it will still not be able to overcome the performance impacts on state updates and re-render. In such scenarios its better to make define state within the children and expose a ref for the parent
As stated, the solution is an anti-pattern because you're mixing the rendering and business logic on both levels. You don't need to explicitly use React.forwardRef, in fact according to the docs you shouldn't, even when composing HOC (Higher order components). You shouldn't need to directly access the element and do some sort of action on it - let React do its thing. It's very good and efficient at it.
Generally when you're calling a re-render method on a child node tree when there's n nodes, you don't want to cause a re-render from the top-level node, the parent in this case, because it will cause the entire node-tree to re-render into a new DOM element, rather than update existing elements.
Your current solution has a combination of parent-triggered renders and child triggered renders. The React page has a good example with the tic-tac-toe application for how to render children without causing the parent to re-render.
The strategy that you should use is one where the parent node has an object structure, in this case n^2 nodes (eg 10x10 for arguments sake), is to pass the rendering functionality to the child nodes, and let the child nodes handle the rendering.
When you're triggering a render from the parent node, you have a couple of options (assuming functional components) which really fall into the case of observable updates. You want to be able to push updates from the parent to the child, to modify the child node state, and let the child node update itself.
Here's an example with child nodes rendering, while the parent is communicating changes to the children. You'll see that the performance scales well even up to massive grids, compared to the nested level renders your example has.
https://codepen.io/jmitchell38488/pen/pogbKEb
This is achieved by using a combination of RxJS observable/subject, React.useState and React.useEffect. We use useState in both the parent and child nodes to deal with rendering and prop updates, and useEffect to bind the observable. useState is persistent between renders, which means you don't need to rebuild the entire grid every time you update in the parent, but even if you do, React is intelligent enough to determine that you updated the props of a node, not replaced it.
const Grid = (props) => {
// When we update the grid, we trigger the parent to re-render
const [grid, setGrid] = React.useState([]);
const subject = new Rx.Subject();
if (grid.length < 1) {
const newGrid = [];
for (i = 0; i < props.h; i++) {
for (k = 0; k < props.w; k++) {
if (!Array.isArray(newGrid[i])) {
newGrid[i] = [];
}
newGrid[i][k] = {
visited: false,
id: `${i}${k}`
};
}
}
setGrid(newGrid);
}
// Tell our node to update
handleClick = (node, visited) => {
subject.next({
id: node.id,
visited: visited
})
};
randomSetAllVisited = () => {
const newGrid = [...grid];
newGrid.forEach(row => {
row.forEach(node => {
node.visited = Math.random() * 2 >= 1;
})
})
// Tell parent to re-render
setGrid(newGrid);
// Because our nodes use `useState`, they are persistent, if the structure of
// grid is the same and the data is mostly the same. This is based on the `key={...}` value
// in row.map, so we need to tell our children nodes to re-render manually
subject.next({
reset: true
})
};
randomSetAnyVisited = () => {
const h = Math.floor(Math.random()*props.h);
const w = Math.floor(Math.random()*props.w);
const node = grid[h][w];
subject.next({
id: node.id,
visited: true
});
};
// Watch console.log to see how frequently parent renders
console.log("rendering parent");
return (
<div>
<table>
<tbody>
{grid.map((row, rowIndex) => (
<tr key={`R${rowIndex}`}>
{row.map((node, columnIndex) => (<GridNode {...node} observer={subject.asObservable()} key={node.id} />))}
</tr>
))}
</tbody>
</table>
<button onClick={randomSetAllVisited}>Random set all visited</button>
<button onClick={randomSetAnyVisited}>Random set any visited</button>
</div>
);
};
const GridNode = (props) => {
// We need to set to undefined to handle full grid reset from parent
const [visited, setVisited] = React.useState(undefined);
// Toggle visited based on props and if visited is undefined
if (props.visited !== visited && visited === undefined) {
setVisited(props.visited);
}
// bind all this with useEffect, so we can subscribe/unsubscribe, and not block rendering, `useEffect` is a good practice
React.useEffect(() => {
// notifications that come from parent node, `setVisited` will re-render this node
const obs = props.observer.subscribe(next => {
if (!!next.id && next.id === props.id) {
setVisited(next.visited !== undefined ? next.visited : !visited);
} else if (!!next.reset) {
setVisited(undefined);
}
});
return () => obs.unsubscribe();
}, [visited]);
handleMouseEnter = () => {
setVisited(!visited);
}
handleMouseLeave = () => {
setVisited(!visited);
}
classes = ["node"];
if (visited) {
classes.push("node-visited");
}
return (<td onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} className={classes.join(" ")}/>);
}
In the codepen example, I have a 50x50 grid, that has no stutters, lag, or issues re-rendering the children nodes, or updating them. There are two helper buttons to randomise the state for all nodes, or randomise a single node. I've scaled this over 100x100 and no lag or performance issues.

Whats the right way to manipulate a model instance in React?

So I have a React component that accepts an instance of a function constructor (a Car).
The component's job is to display information about the Car and manipulate it based on the Car's public interface (methods and properties).
In the example below, a child component should add an accident on button click.
Question: What is the right way for the child to manipulate properties of the Car instance? The root parent's state stores reference to the instance of the Car, and the children are able to manipulate the Car's properties (like .accidents), but see the various onChange examples for why I'm struggling to find the right React way to do this.
I'd like to avoid a heavy handed solution like Flux to store this state.
Any suggestions would be appreciated!
function Car(name, color) {
this.name = name;
this.color = color;
this.accidents = [];
}
const myCar = new Car('Ferrari', 'Red');
myCar.accidents.push('accident #1');
class Accident extends React.Component {
handleButton1 = () => {
const newAccident = 'accident type1 # ' + Math.floor(Math.random()*100);
this.props.onChange1(newAccident);
}
handleButton2 = () => {
const newAccident = 'accident type2 # ' + Math.floor(Math.random()*100);
this.props.onChange2(newAccident);
}
handleButton3 = () => {
const newAccident = 'accident type3 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
this.props.onChange3();
}
handleButton4 = () => {
const newAccident = 'accident type4 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
// This circumvents React's state management, so the parent doesnt
// rerender when its state changes.
}
render() {
return (
<div>
<button onClick={this.handleButton1}>
Add accident (onChange1)
</button>
<button onClick={this.handleButton2}>
Add accident (onChange2)
</button>
<button onClick={this.handleButton3}>
Add accident (onChange3)
</button>
<button onClick={this.handleButton4}>
Add accident (option 4)
</button>
<ul>
{this.props.accidents.map((a, i) => <li key={i}>{a}</li>)}
</ul>
</div>
)
}
}
class DisplayCard extends React.Component {
state = {
editingCar: this.props.car
}
// Push the new accident into state and set it with the same reference.
onChange1 = (newAccident) => {
this.state.editingCar.accidents.push(newAccident);
// Is this semantically different than calling this.forceUpdate?
this.setState({
editingCar: this.state.editingCar,
});
}
// Clone the existing state we want to update and explicitly set that new state
onChange2 = (newAccident) => {
const newAccidentList = _.cloneDeep(this.state.editingCar.accidents);
newAccidentList.push(newAccident);
// Setting our new accident list like this converts editingCar to a POJO
// editingCar.name is lost because a deep merge does not happen.
this.setState({
editingCar: {
accidents: newAccidentList
},
});
}
// Just force update - this.state.editingCar was manipulated by <Accident />.
onChange3 = () => {
this.forceUpdate();
}
render() {
return (
<div>
<div>Car Name: {this.state.editingCar.name}</div>
<Accident
accidents={this.state.editingCar.accidents}
onChange1={this.onChange1}
onChange2={this.onChange2}
onChange3={this.onChange3}
/>
</div>
);
}
}
ReactDOM.render(
<DisplayCard car={ myCar } />,
document.getElementById('container')
);
Also on JSFiddle if you want to play around: https://jsfiddle.net/jamis0n003/fbkn5xdy/4/
EDIT: The React JS docs suggest integrating with "other libraries", such as Backbone models, using forceUpdate:
https://reactjs.org/docs/integrating-with-other-libraries.html#using-backbone-models-in-react-components
When state is stored in a parent component and a child component wants to manipulate that state, the parent should pass a callback function to the child's props. Then the child calls the callback to notify the parent to modify its own state. The child should never modify props since the change can have unintended consequences due to the way objects are referenced in JavaScript.
If you want to get really fancy, you can use Redux which stores "global" state in the top-most parent component. All child components issue (or dispatch) actions which notify the top-level parent to update its state which is then passed down again to all children components through their props.
What is the right way for the child to manipulate properties of the Car instance?
In general, rely on setState() to update state, which will reliably redraw the view, or if you mutate the data use forceRedraw() to ensure the view is redrawn with the latest data -- but using setState() is much preferred. In either case a child must notify a parent of a change using a callback like you have, but instead of having the child Accident actually change the data, make it a "dumb" component which notifies the parent of an intended change and the parent actually makes the change.
I'd like to avoid a heavy handed solution like Flux to store this state.
You may want to look into MobX, which is popular alternative to Flux/Redux that is a bit easier to get into because it allows you to mutate objects very much in the way you are already doing.

Rendering Multiple React Components with .map

This may be a complete noob question, but I am having trouble figuring this out. I have a React component, and inside its render method, I want to render some dynamically passed in components (through props).
Here's my return from the render method (ES6, transpiled with Babel)
return (
<div className={openState}>
/* Shortened for brevity*/
{this.props.facets.map(function(f) {
var CompName = facetMap[f.type]
return <CompName key={f.id} />
})}
/* Shortened for brevity*/
</div>
)
facetMap is simply a map to match up a string to the "type" passed in. I can't seem to get the components to actually render on the page.
I had the result of the .map() method stored in a var, and then logged that to the console, and it showed an array of components. However {facetComponents} in the render method had the same results.
EDIT, for those interested in the solution
Because I genuinely could not find an answer anywhere, here's my completed solution, thanks to #Mathletics
//Components imported using ES6 modules
import OCFacetText from "./oc-facet-text"
// Etc...
//Later on, in my render method:
let facetMap = {
"text": OCFacetText,
"number": OCFacetNumber,
"numberrange": OCFacetNumberRange,
"boolean": OCFacetBoolean,
"checkbox": OCFacetCheckbox,
// Etc, etc...
}
let facetComps = facets.map(function(f) {
var CompName = facetMap[f.type]
return <CompName key={f.id} />
})
//Finally, to render the components:
return (
<div className={openState}>
/* Shortened for brevity*/
{facetComps}
/* Shortened for brevity*/
</div>
)
From what I can gather, it sounds like your facetMap is a collection of strings:
var facetMap = {text: 'OCFacetText'}
but it should actually return component classes:
var OCFacetText = React.createClass({...});
var facetMap = {text: OCFacetText};
Then (and only then) you will need to use the createElement method to construct your component dynamically:
var CompName = facetMap[f.type]; // must be a FUNCTION not a string
return React.createElement(CompName, {key: f.id}) />
Annoying one.
If you want dynamic component names you'll have to utilize React.createElement(CompName), where CompName is a string or the class object.
By default react will look for a component named CompName in your above example.
So your render function would look like so:
return (
<div className={openState}>
/* Shortened for brevity*/
{this.props.facets.map(function(f) {
var CompName = facetMap[f.type]
return React.createElement(CompName, {key: f.id}) />
})}
/* Shortened for brevity*/
</div>
)

react get all children refs

I have a component that renders dynamic children, each of these children need to have a ref assigned to them e.g. ref={'childID' + index}
once the children have loaded I then need a way to loop over of the children and get their refs.
any way to do this?
You should be able to loop through this.refs object using Object.keys.
Object.keys(this.refs).forEach(key =>
const ref = this.refs[key];
...
);
You can use the callback style for the ref prop to collect all the refs.
Here's a rough example of how this would look.
var refs = {};
function refCollector(id) {
return function(element) {
refs[id] = element;
if(allRefsCollected()) {
// do some work with all refs
}
}
}
function allRefsCollected() {
return Object.keys(refs).length >= children.length;
}
return children.map((Child, index) => {
return <Child ref={refCollector(index)} />;
});
When allRefsCollected returns true, you'll know that all the children have been rendered and refs will be an object, mapping id to element.

How to append multiple DOM elements to an element created with reactjs?

I've just started work with reactjs and have not done much hands-on work with it. So far I'm able to create DOM elements through reactjs using JSXTransformer.js. The problem I'm getting is, when I try to create multiple elements within a DOM element, it replaces the old elements with the new ones.
That is, if I want to create div_B, div_C and div_D in mainDiv, it just adds div_D in the mainDiv because it is create last. But I want to append all three divs in the mainDiv.
The code I'm using is following:
var props = [];
function getEle(id) {
return document.getElementById(id);
}
function setProps(ele, Css, inner, id) {
props.element = ele;
props.CssClass = Css;
props.innerText = inner;
props.id = id;
return props;
}
function createElement(properties , element){
var CreateDiv = React.createClass({
render : function(){
return <div className = {this.props.elementProps.CssClass} id={this.props.elementProps.id}>{this.props.innerText}</div>;
}
});
React.render(<CreateDiv elementProps = {properties} />, element);
}
setProps("div", "divBClass", "", "div_B");
createElement(props, getEle("mainDiv"));
setProps("div", "divCClass", "", "div_C");
createElement(props, getEle("mainDiv"));
setProps("div", "divDClass", "", "div_D");
createElement(props, getEle("mainDiv"));
Is there anything wrong with that code?
You are still thinking about your code in an imperative manner. React is based on a declarative programming paradigm.
First, think about your whole application as a React component.
var App = React.createClass({
render: function() {
return (
<div>foo</div>
);
}
})
React.render(<App />, document.body);
Now, let's first render some paragraphs:
var App = React.createClass({
render: function() {
// construct array [0, 1, 2]
var values = [];
for (var i=0; i<this.props.noDivs; i++) {
values.push(i);
}
// return <p>0</p> <p>1</p> ...
return (
<div>
{values.map(function (value) {
return <p key={value}>Value {value}</p>;
})}
</div>
);
}
})
React.render(<App noDivs={3} />, document.body);
If JSX is too much, try to compile it to Javascript. Here is a live example. I'm passing the number of paragraphs as a prop.

Categories

Resources