React Component dynamic item renderer - javascript

I have a list component that renders data from an array and I like to pass the component as property to render each one of the items. Does anyone knows how I can pass a class as a property to my list component and use it to dynamically render the items.

JSX is transformed by babel so every <Component {...props} /> becomes React.createElement('Component', props)
so you can do this
const items = [{
constr: 'Child',
someProp: 'value'
}];
function Parent(props) {
return (<div>
{props.items.map(item =>
React.createElement(item.constr, item)
)}
</div>);
}
<Parent items={items} />
edit:
you can also pass the reference of the class/function
const items = [{
constr: Child, // <- reference
someProp: 'value'
}]
and then take advantage of the object destructing assignment
function Parent(props) {
return (<div>
{props.items.map(({constr: Element, ...props}) =>
<Element {...props} />
)}
</div>);
}

You want a reusable higher order component for that. Something like this:
// -> Build your HOC
function WithItem(Component) {
return function WithItemComponent({ item, ...props }) {
if (item) return (<Component {...props}>{item}</Component>);
return (<Component {...props}/>);
}
}
export default WithItem;
// -> Then Implement it
const WithItemMyComponent = WithItem(MyComponent)
render() {
return (
<WithItemMyComponent item={item} />
)
}

Related

Problems using useRef / useImperativeHandle in mapping components

I have a dashboard with different components. Everything is working with a separate start-button on each component, now I need to have a common start-button, and for accessing the children's subfunctions from a parent, I understand that in React you should use the useRef.(but its perhaps not correct, but I'm struggling to see another way). I would like to have the flexibility to choose which component to start from this "overall start-button"
I have a component list that i map through shown below.
return(
{ComponentsList.map((item) => {
return (
<Showcomponents
{...item}
key={item.name}
/>
)
This works fine, but I would like, as mentioned, to access a function called something like "buttonclick" in each of the children, so I tested this with a pressure-gauge component
The function "exposed" via the forwardRef and the useImparativeHandle
const ShowRadialGauge = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
buttonclick() {
setStart(!start);
},
}));
)
then in my dashboard I changed to :
const gaugepressure = useRef();
return(
<div>
<Button onClick={() => gaugepressure.current.buttonclick()}>
Start processing
</Button>
<ShowRadialGauge ref={gaugepressure} />
<div>
)
This works fine if I use the useRef from the dashboard and instead of mapping over the components, I add them manually.
I understand the useRef is not a props, but its almost what I want. I want to do something like this:
return(
{ComponentsList.map((item) => {
return (
<Showcomponents
{...item}
key={item.name}
**ref={item.ref}**
/>
)
where the ref could be a part of my component array (as below) or a separate array.
export const ComponentsList = [
{
name: "Radial gauge",
text: "showradialgauge",
component: ShowRadialGauge,
ref: "gaugepressure",
},
{
name: "Heatmap",
text: "heatmap",
component: Heatmap,
ref: "heatmapstart",
},
]
Anyone have any suggestions, or perhaps do it another way?
You are on the right track with a React ref in the parent to attach to a single child component. If you are mapping to multiple children though you'll need an array of React refs, one for each mapped child, and in the button handler in the parent you will iterate the array of refs to call the exposed imperative handle from each.
Example:
Parent
// Ref to hold all the component refs
const gaugesRef = React.useRef([]);
// set the ref's current value to be an array of mapped refs
// new refs to be created as needed
gaugesRef.current = componentsList.map(
(_, i) => gaugesRef.current[i] ?? React.createRef()
);
const toggleAll = () => {
// Iterate the array of refs and invoke the exposed handle
gaugesRef.current.forEach((gauge) => gauge.current.toggleStart());
};
return (
<div className="App">
<button type="button" onClick={toggleAll}>
Toggle All Gauges
</button>
{componentsList.map(({ name, component: Component, ...props }, i) => (
<Component
key={name}
ref={gaugesRef.current[i]}
name={name}
{...props}
/>
))}
</div>
);
Child
const ShowRadialGauge = React.forwardRef(({ name }, ref) => {
const [start, setStart] = React.useState(false);
const toggleStart = () => setStart((start) => !start);
React.useImperativeHandle(ref, () => ({
toggleStart
}));
return (....);
});
The more correct/React way to accomplish this however is to lift the state up to the parent component and pass the state and handlers down to these components.
Parent
const [gaugeStarts, setGaugeStarts] = React.useState(
componentsList.map(() => false)
);
const toggleAll = () => {
setGaugeStarts((gaugeStarts) => gaugeStarts.map((start) => !start));
};
const toggleStart = (index) => {
setGaugeStarts((gaugeStarts) =>
gaugeStarts.map((start, i) => (i === index ? !start : start))
);
};
return (
<div className="App">
<button type="button" onClick={toggleAll}>
Toggle All Guages
</button>
{componentsList.map(({ name, component: Component, ...props },, i) => (
<Component
key={name}
start={gaugeStarts[i]}
toggleStart={() => toggleStart(i)}
name={name}
{...props}
/>
))}
</div>
);
Child
const ShowRadialGauge = ({ name, start, toggleStart }) => {
return (
<>
...
<button type="button" onClick={toggleStart}>
Toggle Start
</button>
</>
);
};
#Drew Reese
Thx Drew,
you are off course correct. I'm new to React, and I'm trying to wrap my head around this "state handling".
I tested your suggestion, but as you say, its not very "React'ish", so I lifted the state from the children up to the parent.
In the parent:
const [componentstate, setComponentstate] = useState([
{ id:1, name: "pressuregauge", start: false},
{ id:2, name: "motormap", start: false },
{ id:3, name: "heatmapstart", start: false},
]);
then in the component ShowRadialGauge, I did like this:
const ShowRadialGauge = ({ props, componentstate })
and if we need to keep the button in each component, I have the id in the componentstate object that is desctructured, so I can send that back.
.
First of all, why do you need refs to handle click when you can access it via onClick. The most common use case for refs in React is to reference a DOM element or store value that is persist between renders
My suggestion are these
First, try to make it simple by passing a function and then trigger it via onClick
Second if you really want to learn how to use imperativeHandle you can reference this video https://www.youtube.com/watch?v=zpEyAOkytkU.

console.log shows that the prop has attributes name and number, but they are undefined

So I have the following code, where I would like to return the number of pages in a book inside of the Part component in the return props.pages. The console.log(props) prints out the attributes of the book (name and pages). However when accessing them in the Part component via props.pages or props.name it returns undefined?
How would I go about fixing this
const Part = (props) => {
console.log(props);
return <p>{props.pages}</p>;
};
const Content = (props) => {
console.log(props);
return <Part props={props} />;
};
const App = () => {
const book1 = {
name: "Harry Potter",
pages: 108,
};
return (
<div>
<Content book={book1} />
</div>
);
};
export default App;
Try this :
const Part = (data) => {
const { props } = data;
return <p>{props.book.pages}</p>
};
Demo : https://codesandbox.io/s/vigorous-jepsen-lqjs01?file=/src/App.js:24-113
You've wrapped the "props" in another "props". When you do this:
<Content book={book1} />
The props object in the Content component has a property called book. So when you do this:
<Part props={props} />
The props object in the Part component has a property called props. That property is an object with a property called book. You'd need to reference that property:
return <p>{props.props.pages}</p>;
Alternatively, you can pass the values more explicitly instead:
<Part pages={props.pages} />
Or spread the properties of the props object to implicitly pass all of them:
<Part {...props} />
In either of these cases you'd be able to access the property directly:
return <p>{props.pages}</p>;

Iterating over child components and rendering each wrapped in a higher order component

I am currently experiencing an issue when it comes to wrapping the child elements of a parent element in a higher order component and then rendering them.
Consider the following structure:
return (
<div className="App">
<FocusProvider>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
<TestComponent testProp={'Foo'}/>
</FocusProvider>
</div>
);
Where FocusProvider is the parent element and TestComponent is the child element that needs to be wrapped in a higher order component that provides lifecycle methods to it as well as inject props.
And then the higher order component called hoc which overrides the prop for TestComponent and provides a lifecycle method to it as well looks like:
const hoc = (WrappedComponent, prop) => {
return class extends React.Component {
shouldComponentUpdate = (prevProps, prop) => {
return !prevProps === prop
}
render(){
return <WrappedComponent testProp={prop}/>
}
}
}
The render method of FocusProvider looks like :
render(){
return(
this.props.children.map(child => {
let Elem = hoc(child, 'bar')
return <Elem/>
})
)
}
When I try and render that I get Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
When I try and change it to :
render(){
return(
this.props.children.map(child => {
let elem = hoc(child, 'bar')
return elem
})
)
}
Nothing is returned from render. I am confused because I can render the chil components directly, but not the child components wrapped in the HOC:
render(){
return(
this.props.children.map(child => {
return child //This works
})
)
}
I want to avoid using React.cloneElement as I don't want to trigger re-renders by cloning the child elements every time the parent updates.
Any help would be appreciated
hoc is a function which returns a Component not jsx. You cannot wrap the children in a HOC like that.
But you can wrap just FocusProvider and pass the prop down to it's children using cloneElement. There is no problem in use cloneElement like this. Is a common pattern actually.
function App() {
return (
<div className="App">
<FocusProvider bar="baz">
<Child />
<Child />
<Child />
</FocusProvider>
</div>
);
}
const withHOC = Component => props => {
return <Component foo="bar" {...props} />;
};
const FocusProvider = withHOC(({ children, foo, bar }) => {
return React.Children.map(children, child => {
return React.cloneElement(child, { foo, bar });
});
});
const Child = ({ foo, bar }) => (
<>
{foo}
{bar}
</>
);

React/ES6: curly braces in function signature? [duplicate]

I know you can pass all a react components props to it's child component like this:
const ParentComponent = () => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...this.props} />
</div>
)
But how do you then retrieve those props if the child component is stateless? I know if it is a class component you can just access them as this.prop.whatever, but what do you pass as the argument into the stateless component?
const ChildComponent = ({ *what goes here?* }) => (
<div>
<h1>Child Component</h1>
</div>
)
When you write
const ChildComponent = ({ someProp }) => (
<div>
<h1>Child Component {someProp}</h1>
</div>
)
From all the props that you are passing to the childComponent you are just destructuring to get only someProp. If the number of props that you want to use in ChildComponents are countable(few) amongst the total number of props that are available, destructuring is a good option as it provides better readability.
Suppose you want to access all the props in the child component then you need not use {} around the argument and then you can use it like props.someProp
const ChildComponent = (props) => (
<div>
<h1>Child Component {props.someProp}</h1>
</div>
)
Are you looking for the ES6 named argument syntax (which is merely destructuring) ?
const ChildComponent = ({ propName }) => (
<div>
<h1>Child Component</h1>
</div>
)
const ChildComponent = (props) => ( // without named arguments
<div>
<h1>Child Component</h1>
</div>
)
Optionally there is a second argument to your function depending of whether you specified a context for your component or not.
Perhaps it would be more helpful wityh a links to the docs. As stated in the first article about functional components. Whatever props passed on to the component is represented as an object passed as first argument to your functional component.
To go a little further, about the spread notation within jsx.
When you write in a component :
<Child prop1={value1} prop2={value2} />
What your component will receive is an plain object which looks like this :
{ prop1: value1, prop2: value2 }
(Note that it's not a Map, but an object with only strings as keys).
So when you're using the spread syntax with a JS object it is effectively a shortcut to this
const object = { key1: value1, key2: value2 }
<Component {...object}/>
Is equivalent to
<Component key1={value1} key2={value2} />
And actually compiles to
return React.createElement(Component, object); // second arg is props
And you can of course have the second syntax, but be careful of the order. The more specific syntax (prop=value) must come last : the more specific instruction comes last.
If you do :
<Component key={value} {...props} />
It compiles to
React.createElement(Component, _extends({ key: value }, props));
If you do (what you probably should)
<Component {...props} key={value} />
It compiles to
React.createElement(Component, _extends(props, { key: value }));
Where extends is *Object.assign (or a polyfill if not present).
To go further I would really recommend taking some time to observe the output of Babel with their online editor. This is very interesting to understand how jsx works, and more generally how you can implement es6 syntax with ES5 tools.
const ParentComponent = (props) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...props} />
</div>
);
const ChildComponent = ({prop1, ...rest}) =>{
<div>
<h1>Child Component with prop1={prop1}</h1>
<GrandChildComponent {...rest} />
</div>
}
const GrandChildComponent = ({prop2, prop3})=> {
<div>
<h1>Grand Child Component with prop2={prop1} and prop3={prop3}</h1>
</div>
}
You can use Spread Attributes reducing code bloat. This comes in the form of {'somearg':123, ...props} or {...this.props}, with the former allowing you to set some fields, while the latter is a complete copy. Here's an example with ParentClass.js :
import React from 'react';
import SomeComponent from '../components/SomeComponent.js';
export default class ParentClass extends React.Component {
render() {
<SomeComponent
{...this.props}
/>
}
}
If I do, <ParentClass getCallBackFunc={() => this.getCallBackFunc()} />, or if I do <ParentClass date={todaysdatevar} />, the props getCallBackFunc or date will be available to the SomeComponent class. This saves me an incredible amount of typing and/or copying/pasting.
Source: ReactJS.org: JSX In Depth, Specifying the React Element Type, Spread Attributes. Official POD:
If you already have props as an object, and you want to pass it in JSX, you can use ... as a “spread” operator to pass the whole props object. These two components are equivalent:
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}```
Now, let's apply this to your code sample!
const ParentComponent = (props) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...props} />
</div>
);
I thought I would add a simple ES2015, destructuring syntax I use to pass all props from a functional parent to a functional child component.
const ParentComponent = (props) => (
<div>
<ChildComponent {...props}/>
</div>
);
Or if I have multiple objects (props of parent, plus anything else), I want passed to the child as props:
const ParentComponent = ({...props, ...objectToBeAddedToChildAsProps}) => (
<div>
<ChildComponent {...props}/>
</div>
);
This destructuring syntax is similar to the above answers, but it is how I pass props along from functional components, and I think it is really clean. I hope it helps!
But how do you then retrieve those props if the child component is stateless?
const ChildComponent = ({ *what goes here?* }) => (
<div>
<h1>Child Component</h1>
</div>
)
ChildComponent holds the name and the props will be the argument in the arrow function syntax just as you need:
const ChildComponent = props => (
<div>
<p>{props.value ? props.value : "No value."}</p>
</div>
);
If you Babel-it it will create something like this:
var ChildComponent = function ChildComponent(props) {
return React.createElement(
"div",
null,
React.createElement(
"p",
null,
props.value ? props.value : "No value."
)
);
};
For some reason, what seems to work for me is a variation on Shubham's answer above:
const ChildComponent = props => (
<div>
<h1>Child Component {props[0].someProp}</h1>
</div>
)
Using this
const ParentComponent = ({ prop1, prop2, prop3 }) => (
<div>
<h1>Parent Component</h1>
<ChildComponent {...{ prop1, prop2, prop3 }} />
</div>
);
const ChildComponent = ({ prop1, prop2, prop3 }) =>{
<div>
<h1>Child Component with prop1={prop1}</h1>
<h1>Child Component with prop2={prop2}</h1>
<h1>Child Component with prop2={prop3}</h1>
</div>
}

React functional component - how do I pass refs back to parent when component returns an array of elements?

I have a React component that includes a stateless functional component. The inner component runs Lodash map on an array of values to return an array of p tags.
class Application extends React.Component {
items = [
'first',
'second',
'third',
];
render() {
return <div>
<Paragraphs items={ this.items } />
</div>;
}
}
const renderItem = ( item, index ) => {
return (
<p key={ index }>{ item }</p>
);
};
const Paragraphs = ( { items } ) => _.map( items, renderItem );
ReactDOM.render(<Application />, document.getElementById('root'));
My Application component needs references to these DOM elements, so I'd like to pass back a ref for each of the p tags back to the parent component. Can anyone suggest the best way to do this? All the examples I've found assume the child component is a single element.
Codepen example
Now in React 16.3 you can create refs with React.createRef() and pass them from parent component to child. Here is the docs.
So you can map items in the parent component and extend them with ref property.
I hope this will work for you.
class Application extends React.Component {
items = [
'first',
'second',
'third',
].map(item => ({ item, ref: React.createRef() }))
// you can access refs here: this.items[0].ref
render() {
return <div>
<Paragraphs items={this.items} />
</div>;
}
}
const renderItem = (item, index) => {
return (
<p key={index} ref={item.ref} > {item.item} </p>
);
};
const Paragraphs = ({ items }) => _.map(items, renderItem);
ReactDOM.render(<Application />, document.getElementById('root'));
for starters, you may want to read in here -
https://reactjs.org/docs/refs-and-the-dom.html
https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509
And then, if you have grasped the concept of references in react, I have done, some very simple changes( see below ) to the JS file from your codepen's pen.
I am pasting the new pen, with needed specs here - https://codepen.io/anon/pen/wjKvvw
class Application extends React.Component {
items = [
'first',
'second',
'third',
];
item_refs = this.items.map(a=>{}) // making an empty list for references
item_referer = (ele, ind) => { // a callback function to be called in the children where references are to be made
this.item_refs[ind] = ele;
console.log("Referring to", this.items[ind], ele) // a simple logging to show the referencing is done. To see open the console.
}
// passing the item_referer function as the prop (itemReferer) to children
render() {
return <div>
<Paragraphs items={ this.items } itemReferer={ this.item_referer }/>
</div>;
}
}
const renderItem = ( item, index, referToMe ) => {
// referToMe is the callback function to be called while referencing
return (
<p key={ index } ref={(el)=>referToMe(el, index)} >{ item }</p>
);
};
// get the itemReferer prop passed to Paragraphs component and use it
// render the children.
const Paragraphs = ( { items, itemReferer } ) => items.map((item, index )=>{
return renderItem(item, index, itemReferer) // passing to refer to the individual item
})
ReactDOM.render(<Application />, document.getElementById('root'));
Go through the code, if you have any questions, let me know :)

Categories

Resources