Passing props to components rendered dynamically from object - javascript

I have an object full of components:
const Icons = {
// commonly used darksky icon names
"clear-day": <ClearDayIcon />,
"clear-night": <ClearNightIcon />,
"rain": <RainMediumIcon />,
"snow": <SnowIcon />,
"sleet": <RainHeavyIcon />,
"wind": <WindyDayIcon />,
// etc..
}
Each component is really just an svg wrapped as a react component. But I need it to receive props from the place where its called, especially the className prop.
What I want to be able to do is call these components from my other components in the following manner:
<WeatherIcon icon={icon} className="some-class" />
Where the icon prop will determine which icon component is chosen. So I tried this:
const WeatherIcon = props => Icons[ props.icon ]
So this works partially, as I can write <WeatherIcon icon={'clear-night'} />, and the correct component is rendered. However, there's no way to pass any other props from my WeatherIcon component down through each Icon. For example, writing <WeatherIcon icon={'clear-night'} className="some-class" /> clearly does not pass the className prop (or any other prop) down through to each individual component. I tried doing something like this:
const Icons = {
"clear-day": props => <ClearDayIcon {...props} />,
"clear-night": props => <ClearNightIcon {...props} />,
// etc..
}
But this doesn't work because now I'm returning a Component rather than a <Component />. I saw the solutions in the question Passing props to dynamically loaded components, but these all suggest calling the component like { Icons['clear-day'](className: 'some-class', anotherProp: 'some-prop') }. I feel like this is not very elegant. There must be a way to write it as a <WeatherIcon icon={'some-icon'} className={'some-class'} someProp={'some-prop'} />, and have the props filter down correctly. (I do realize that all the props would filter all the way down to my SVG component - that's fine). I feel like theres a higher order component waiting to be written here, but right now its eluding me.
Thanks for reading

I'm not 100% sure about if this will fulfill your requirement but I would probably try something like this;
const Icons = {
"clear-day": ClearDayIcon,
"clear-night": ClearNightIcon,
"rain": RainMediumIcon,
"snow": SnowIcon,
"sleet": RainHeavyIcon,
"wind": WindyDayIcon,
// etc..
}
const Icon = ({icon, ...rest}) => {
const IconComponent = Icons[icon]
if(!IconComponent) {
// Or throw an exception maybe.
// At least print some console warnings in development env.
return null;
}
return <IconComponent {...rest} />
}
With this way, you can select one of your Icon components and pass any prop to it.
And when you want to use it, you can use it like this;
// ...
<Icon icon="clear-day" className="some-class" />
// ...

I figured out that this works too - write the object containing the components as a function of props, which returns the object. Then you can use a {...props} in each component:
const Icons = props => ({
"clear-day": <ClearDayIcon {...props} />,
"clear-night": <ClearNightIcon {...props} />,
"rain": <RainMediumIcon {...props} />,
etc.
})
Then the wrapper component looks like this:
const WeatherIcon = props => Icons(props)[ props.icon ]
Pretty simple - I knew I was close. I'm not sure if this is considered a higher order component (HOC). It is a component that returns another component based on its props, with new props attached. Does that count as an HOC?

Related

Spread operator to pass all other props to a component. React.js

I'm having trouble understanding the spread operator when I want to pass all other props to a component.
Any help would be appreciated.
import React, { Fragment } from "react";
import SiteCard from "./SiteCard";
const SiteList = ({ sites }) => {
return (
<Fragment>
{sites.map((site) => {
return (
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
otherSiteProps={...site} // how can I pass the site props here?
/>
);
})}
</Fragment>
);
};
export default SiteList;
You are almost there with the solution.
You need to pass it as otherSiteProps={{...site}}.
This is if you want to pass site as an object to otherSiteProps property of SiteCard.
If you want to spread site and have multiple props for component SiteCard you do it like this:
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
{...sites}
/>
This in case that sites is an object. If site is an array, this wont work.
You just need to write:
<SiteCard
key={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
{...site} // how can I pass the site props here?
/>
But wait, why you're making so complicated? You can just use:
<SiteCard {...site} />
Now, in your SiteCard component use required props.
And if I were you, I would not have separated SiteCard component for this scenario. I would just write:
{sites.map((site) => {
return (
// everything here I will utilize in html.
);
})}

How to pass React Component as props to another component

I am trying to call PopupDialog.tsx inside Content.tsx as a sibling of Item.tsx.
Previously PopupDialog.tsx is called inside C.tsx file but due to z index issue i am trying to bring it out and call it in Content.tsx
Is it possible to somehow pass the whole component(popupDialog and its parameters) in Content.tsx so that i could avoid passing back and forth the parameters needed for popupdialog in content.tsx.
Code in C.tsx where PopupDialog component is called.
const C = (props: Props) => (
<>
{props.additionalInfo ? (
<div className="infoButton">
<PopupDialog // need to take this code out and want to add in Content.tsx
icon="info"
callback={props.callback}
position={Position.Right}
>
<div className="popuplist">{props.additionalInfo}</div>
</PopupDialog>
</div>
) : (
<Button className="iconbutton"/>
)}
</>
);
Content.tsx where i would like to call PopupDialog.tsx with its parameters
const Content = (props: Props) => {
const [componentToRender, docomponentToRender] = React.useState(null);
const [isAnimDone, doAnim] = React.useState(false);
return (
<div className="ContentItems">
<PWheel agent={props.agent} />
{isAnimDone && (
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog/> //want to call here with all its parameters to be passed
}
</>
)}
</div>
);
};
Folder Structure
App.tsx
->ViewPort.tsx
->Content.tsx
->PWheel.tsx
->Item.tsx
->A.tsx
->B.tsx
->C.tsx
{props.additionalinfo &&
->PopupDialog.tsx
->PopupDialog.tsx
So if I understand the question correctly you want to pass one component into another so that you can use the properties or data of the passed componenet in your current component.
So there are three ways to achieve this.
1)Sending the data or entire component as prop.This brings disadvantage that even though components which don't require knowledge
about the passed component will also have to ask as a prop.So this is bascially prop drilling.
2)The other is you can use context api.So context api is a way to maintain global state variale.so if you follow this approach you don't need to pass data or componenet as props.Wherever you need the data you can inport context object and use it in componenet.
3)Using Redux library.This is similar to context api but only disadavantage is that we will have to write lot of code to implement this.Redux is a javascript library.
Let me know if you need more info.
You need to :
<>
<Item {props.agent} />
{componentToRender &&
<PopupDialog abc={componentToRender} /> //you must call in this component, in this case i name it is abc , i pass componentToRender state to it
}
</>
and then PopupDialog will receive componentToRender as abc, in PopupDialog , you just need to call props.abc and done .
If you need to know more about prop and component you can see it here
I think what you want to use is Higher-Order-Components (HOC).
The basic usage is:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Below is such an implementation that takes a component (with all its props) as a parameter:
import React, { Component } from "react";
const Content = WrappedComponent => {
return class Content extends Component {
render() {
return (
<>
{/* Your Content component comes here */}
<WrappedComponent {...this.props} />
</>
);
}
};
};
export default Content;
Here is the link for higher-order-components on React docs: https://reactjs.org/docs/higher-order-components.html
Make use of
useContext()
Follow this for details:
React Use Context Hook

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

Send props to an array of components

I have an array of components that I want to render, but I don't know how to send props to each of them. Do you have any idea how? I am using also using formik for form management and the page components consist of basic inputs.
const pages = [<Page1 />, <Page2 />, <Page3 />, <Page4 />]
and I am rendering them this way:
<div>{pages[state]}</div>
You can accomplish this by using React.cloneElement:
const newProps = {}
<div>{React.cloneElement(pages[state], newProps)}</div>
React.cloneElement
There are 2 options to achieve this:
Option 1: On the declaration site you may pass the state while initialising the array.
const pages = [<Page1 props={page1Prop: 'value'}/>,
<Page2 props=page2Props />,
<Page3 props=page3Props />,
<Page4 props=page4Props />]
Or if you do not want or can not update the declaration you may pass when rendering:
Or you can
Option 2: clone the component prop and apply the new props
...
render() {
return (
<div>
{React.cloneElement(pages[currentPage] propsForPage[currentPage])}
OR
{React.cloneElement(pages[currentPage] {someProp:'value'})}
</div>
);
}
}
Could you not initialise the component in the array?
const pages = [Page1, Page2, Page3, Page4]
return (
<div>
{pages.map(page => {
const Component = <page />
return (
<Component {...props} />
)
}
</div>
)

How to write a wrapper around a material UI component using React JS?

I am using Material UI next library and currently I am using List component. Since the library is in beta, lot of its parameter names get changed. To solve this I am planning to write a wrapper around the required components so that things wont break. My list component :
<List dense>
<List className={classes.myListStyles}>
<ListItem disableGutters/>
</List>
</List>
How should I write the wrapper for the List(say myListWrapper) and ListItem so that the wrapper component can handle props and pass them to the actual MUI list component inside?
I had worked on MUI wrappers, writing my own library for a project. The implementation we are focusing, is to pass the props to inner/actual-MUI component from the our wrapper component. with manipulation. In case of wrapping props for abstraction.
Following is my approach to the solution:
import { List as MaterialList } from 'material-ui/List';
import { React } from 'react';
import { ListItem as MaterialListI } from 'material-ui/ListItem';
class List extends MaterialList {
constructor(props){
const propsToPass = {
prop1 : change(props.prop1),
...props
}
super(propsToPass);
}
};
class ListItem extends MaterialListItem {
const propsToPass = {
prop1 : change(props.prop1),
prop2 : change(props.prop2),
...props
}
super(propsToPass);
}
};
class App extends React.Component {
render () {
return (
<List prop='value' >
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
</List>
)
}
};
Above code will allow following things to do with your component:
You can use the props with exact names, as used in Material UI.
You can manipulate/change/transform/reshape you props passed from outside.
If props to you wrapper components are passed with exactly same names as MUI is using, they will directly be sent to the inner component. (... operator.)
You can use Component with exact same name as material is using to avoid confusion.
Code is written according to advance JSX and JavaScript ES6 standards.
You have a space to manipulate your props to pass into the MUI Components.
You can also implement type checking using proptypes.
You can ask for any confusion/query.
You can write it like this:
const MyList = props => (
<List
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</List>
)
const MyListItem = props => (
<ListItem
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</ListItem>
)
Now you need to use MyList and MyListItem, decide the prop names for these component (as per your convenient), and inside these component map those values to actual Material-UI component properties.
Note:
If you are using the same prop names (same name as material-ui component expect) for your component then you can write like this also:
const MyList = ({children, ...rest}) => <div {...rest}>{children}</div>
const MyListItem = ({children, ...rest}) => <p {...rest}>{children}</p>
Check this example:
const A = props => <div>{props.children}</div>
const B = props => <p>{props.children}</p>
ReactDOM.render(
<A>
<A>
<B>Hello</B>
</A>
</A>,
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' />

Categories

Resources