Destructuring and passing in full object simultaenously - javascript

I have a simple React component:
const FullContainer = ({
backgroundColor,
children,
}) => (
<Container
backgroundColor={backgroundColor}
>
{children}
</Container>
);
I'm currently destructing the only two properties I expect my component to use, but I'd also like to pass in props and spread it in as well:
const FullContainer = (props, {
backgroundColor,
children,
}) => (
<Container
backgroundColor={backgroundColor}
{...props}
>
{children}
</Container>
);
Oddly enough, this breaks my page with no errors. I must be doing something wrong. Is my syntax wrong?

You can make use of rest spread syntax that provides the remaining properties which aren't destructured as an array like
const FullContainer = ({
backgroundColor,
children,
...props
}) => (
<Container
backgroundColor={backgroundColor}
{...props}
>
{children}
</Container>
);

Related

How to use prop spreading and extracting props at the same time

I have two components like this
const TextLarge = (props) => { // props are classes,color and children
return <Text
classes={'text-large-style'}
{...props}>
{props.children}
</Text>
}
const Text = ({
children,
color,
classes,
}) => {
return <p
className={clsx('text-styles', classes)}
style={{color: color}}>
{children}
</p>
}
Now, when I pass a class to <TextLarge />
<TextLarge
classes='some-special-style'>
Foo
<TextLarge>
This overwrites classes='text-large-style' because {...props} spreads classes='some-special-style into <TextLarge/> after classes='text-large-style.
Is there a (React/elegant) way to take classes out of props, so something like
const TextLarge = (props) => { // props are classes,color and children
return <Text
classes={clsx('text-large-style', props.classes)}
{...props}> // no classes in here
{props.children}
</Text>
}
yet still spreading the rest of the {...props} into <Text />?
I know that could change the order, making classes={clsx('text-large-style', props.classes)} overwrite {...props}
const TextLarge = (props) => { // props are classes,color and children
return <Text
{...props}
classes={clsx('text-large-style', props.classes)}>
{props.children}
</Text>
}
but I want more control.
Is there a (React/elegant) way to take classes out of props
Yes, you can use destructuring with a rest target:
const TextLarge = ({classes, ...rest}) => {
// ...
};
Actually, you probably want to remove children as well:
const TextLarge = ({classes, children, ...rest}) => {
// ...
};
Then ...rest won't include classes (or children), and you can include classes in your explicit prop (if you want to pass them on):
const TextLarge = ({classes, children, ...rest}) => {
return (
<Text {...rest} classes={`text-large-style ${classes ?? ""}`}>
{children}
</Text>
);
};

How to render react children as a <Children /> and not {children} [duplicate]

This question already has answers here:
How to pass props to {this.props.children}
(32 answers)
Closed 1 year ago.
I have a simple component:
const Test = ({header}) => <View> <Header /> </View>`
and call it like so:
<Test header={<View> Test </View} />
but it fails saying:
JSX element type 'Header' does not have any construct or call signatures
How do I fix this? I need to render it like <Header /> so I can pass further props down to it
I don't want to render it like {header} this.
If you want to pass props to your children have a look at the React API with the function cloneElement
You can foreach your children using React.Children
function Component({ children, ...props }) {
const newChildren = React.Children.map(children, child => {
return React.cloneElement(child, { ...child.props, ...props });
});
return (
<div>
{newChildren}
</div>
);
}
You can make it like this:
const Test = ({children}) => <View> {children} </View>
and use it like this:
<Test>
<Header/>
</Test>
For more read the docs composition-vs-inheritance
Let me know if you are trying to achieve this
const Test = ({header: Header}) => <View> <Header /> </View>
you can try to do this
const Test = ({header})=>{
return <View>
{React.cloneElement(header, ObjectContainingPropsYouWantToPass)}
</View>
}
source

withSyles HOC does not pass innerRef along with other props when passing through React.forwardRef

I'm passing the ref using React.forwardRef to the down component which usually works.
<SomeComponent
component={React.forwardRef((props, ref) => <MyComponent innerRef={ref} {...props} />)}
.../>
However when I have HOC (higher order component) withStyles, the innerRef along with other props do not get passed properly.
// innerRef does not exists in props
const MyComponent = withStyles(styles)(({ one, two, ...props }) => {
return (
<Fragment>
<NavLink {...props}></NavLink>
...
</Fragment>
);
})
Without using withStyles I get them perfectly
// innerRef exists in props
const MyComponent = ({ one, two, ...props }) => {
return (
<Fragment>
<NavLink {...props}></NavLink>
...
</Fragment>
);
}
How can I still have the withStyles HOC but with innerRef and other props included?
The issue appeared after migration from material ui v3 to v4. NavLink requires the innerRef property.
withStyles passes along innerRef as ref, so something like the following should work:
const MyComponent = withStyles(styles)(({ one, two, ...props }, ref) => {
return (
<Fragment>
<NavLink {...props} ref={ref}></NavLink>
...
</Fragment>
);
})
Or if you need to keep it as innerRef on NavLink:
const MyComponent = withStyles(styles)(({ one, two, ...props }, ref) => {
return (
<Fragment>
<NavLink {...props} innerRef={ref}></NavLink>
...
</Fragment>
);
})
My recommendation based on my comments:
<SomeComponent
component={React.forwardRef((props, ref) => <MyComponent nRef={ref} {...props} />)}
.../>
const MyComponent = withStyles(styles)(({ one, two, ...props }) => {
props.innerRef = nRef;
return (
<Fragment>
<NavLink {...props}></NavLink>
...
</Fragment>
);
})
Or alternatively
<NavLink {...props} innerRef={props.nRef}></NavLink>

Is there any other way to add attributes to a component in React?

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Is there any way to add attributes to the Welcome function-component, other than by creating a separate line such as <Welcome name="Sara" />?
Using .map() you can render the elements of an array as:
function App() {
const elements = ["Sara", "Cahal", "Edite"]
return (
<div>
{ elements.map((e, i) => <Welcome key={i} name={e} />) }
</div>
);
}
Additionally I suggest to read through the documentation of Rendering Elements and Lists and Keys.
This is not the standard way of defining props, but I quite like the syntax of using an object with the spread operator to define props if you have variables with the names of the props you want.
function App() {
const elements = ['Sara', 'Cahal', 'Edite'];
return (
<div>
{elements.map((name, key) => (
<Welcome {...{ name, key }} />
))}
</div>
);
}
You can create an array with names and after loop through it:
const names = ["Sara", "Cahal", "Edite"];
return (
<div>
{names.map(name => <Welcome key={name} name={name} />)}
</div>
)

Trying to iterate over a JSON object in React

I am trying to display a toggle checkbox for every JSON value I have. This is what the json object looks like for element
{
"sourceIP": {
"Primary": ["237.100.100.3", "238.0.4.8"],
"Secondary": ["237.0.1.178", "237.1.1.91"]
},
"multicastIP": {
"Primary": ["10.208.153.129", "238.0.4.8"],
"Secondary": ["10.208.133.58", "238.0.4.8"]
}
}
So I would like to iterate through element instead of hardcoding it like this:
const CustomToggle = ({ element}) => (
<List divided verticalAlign='middle'>
<Segment textAlign='left' style={{paddingLeft: '7.5em'}}>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Primary[0]))}
value={(JSON.stringify(element.sourceIP.Primary[0]))}
/>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Primary[1]))}
value={(JSON.stringify(element.sourceIP.Primary[1]))}
/>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Secondary[1]))}
value={(JSON.stringify(element.sourceIP.Secondary[1]))}
/>
</Segment>
</List>
);
I have been trying to forEach and .map but none of this seems to work. I don't want to have to hardcode every json value in element. How can I just iterate through my element JSON object?
Object.keys(element).reduce((a,c) => a.concat(...Object.values(element[c])),[])
this transformed data gives array of ip adtresses. So, now you can iterate it
const CustomToggle = ({ element}) => {
const elements = Object.keys(element).reduce((a,c) => a.concat(...Object.values(element[c])),[]);
return (
<List divided verticalAlign='middle'>
<Segment textAlign='left' style={{paddingLeft: '7.5em'}}>
{elements.map(ip => <Checkbox toggle label={ip} value={ip} />}
</Segment>
</List>
)
}
Here try this, let me know if it helps :)
const CustomToggle = ({ element }) => (
<List divided verticalAlign="middle">
<Segment textAlign="left" style={{ paddingLeft: "7.5em" }}>
{Object.keys(element).map(key => {
return [...element[key].Primary, ...element[key].Secondary].map(x => {
<Checkbox toggle label={x} value={x} />;
});
})}
</Segment>
</List>
);
You can for example do something like the snipped below:
const List = ({ divided, rest }) => <div {...rest} />;
const Checkbox = ({ toggle, ...props }) => (
<div>
<input type="checkbox" {...props} />
<label>{props.label}</label>
</div>
);
const Segment = (props) => <div {...props} />;
const CustomToggle = ({ element }) => (
<div>
{element.map((ip) => (
<Checkbox toggle label={ip} value={ip} />
))}
</div>
);
function App() {
let result = `{
"sourceIP": {
"Primary": ["237.100.100.3", "238.0.4.8"],
"Secondary": ["237.0.1.178", "237.1.1.91"]
},
"multicastIP": {
"Primary": ["10.208.153.129", "238.0.4.8"],
"Secondary": ["10.208.133.58", "238.0.4.8"]
}
}`;
let data = JSON.parse(result);
let ips = [];
Object.keys(data).map((source) => {
Object.keys(data[source]).map((type) => {
ips.push(...data[source][type]);
});
});
return <CustomToggle element={ips} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So, you'll parse the data, then iterate over it to get the IPs and then pass those as a prop to the CustomToggle. The code above is just an example to get you going as I don't know what your List, Checkbox and Segment components look like.
The reasoning is that CustomToggle should not know or care what the data should look like, it just knows how to render a custom toggle based on some string that it receives, in this case the ip. You should however make sure that each Checkbox has a unique key.

Categories