Passing string from parent to render multiple children - javascript

I may lose something on the way.. I want to pass object arguments to a children to dinamically render it in three different ways.
Here is my object:
const cards = [
{
imgSrc: "first",
desc: "some text"
},
{
imgSrc: "second",
desc: "some text"
},
{
imgSrc: "third",
desc: "some text"
}
];
This is the children component:
import { Box } from '#mui/system'
import React from 'react'
import "../../style/main.scss"
import first from "../../images/veloce.png"
import second from "../../images/sicuro.png"
import third from "../../images/vicino.png"
import { Typography } from '#mui/material'
function Card( source, text ) {
return (
<Box className="foundation-card">
<img src={source}/>
<Typography variant="h6">{text}</Typography>
<Typography variant="body2">{text}</Typography>
</Box>
)
}
export default Card
And then i have the parent component where i want to render multiple Card mapping the cards array:
import Card from "./Card"
import CustomDivider from "../foundation/CustomDivider"
function Values() {
return (
<Box className="vertical-box main-maxw section-margin">
<Typography variant="h4">Perchè sceglierci</Typography>
<CustomDivider />
<Box className="foundation-box values">
{cards.map((p) => {
return <Card source={p.imgSrc} text={p.desc} />
})}
</Box>
</Box>
)
}
export default Values
and then i receive this:
Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
I suppose it is a stupid error but i am in my first approach and i don't know how to move.
Thank you all.

I think the problem is that your card function is expecting positional arguments but you're calling it with an object.
<Card source={p.imgSrc} text={p.desc} />
// These two things are equivalent.
Card({source: p.imgSrc, text: p.desc})
So essentially you were assigning the source arg and object that contained both source and text.
Try changing your card function to accept an object
function Card({source, text}) {
...
}

I see some strange things.
1- where you are mapping the cards, you are not importing the cards array, or i cant see where are you getting the cards array from
2- function card is a react component so you should wrap the props with {} as this: function Card({source,text})

Related

target specific HTML element from an object that is mapped

So I have replicated my issue on a smaller level so its easier to work with. The goal is to have an onclick function that shows the content inside the column which is hidden as default, this will ideally be handled through setting display: none and changing to display: flex. I have to use an object hence lodash. I cant use document.getElement because all mapped components share a common classname. The data comes from a database so the amount of columns is unknown and always changing per user requirements.
I guess im looking for a method to target to the classname of the div/tag of the iteration of the map where the onclick for that container is. Any help is appreciated!
import _ from "lodash"
import React from 'react'
const dummyData = {
columnOne: "item one",
columnTwo: "item two",
columnThree: "item three"
}
function TestPage() {
return (
_.map(dummyData, (data, key) => {
return (
<>
<div className="column-container">
<p className="heading">{key}</p>
<div className="show-content-btn">V</div> //button to toggle content visibility
</div>
<p className="content">{data}</p> //content to be hidden/shown
</>
)
}
)
)
}
export default TestPage
You can use useState to store classnames and then toggle them with onClick function by changing state with setState.
See this answer. I think it contains what you want.
One way of doing this is by making a separate component for HTML which return from map function and handle state in it. Like:
import _ from "lodash";
import React from "react";
const dummyData = {
columnOne: "item one",
columnTwo: "item two",
columnThree: "item three"
};
function Data({ id, data }) {
const [show, setShow] = React.useState(false);
return (
<>
<div className="column-container">
<p className="heading">{id}</p>
<div className="show-content-btn">
<button onClick={() => setShow(!show)}>V</button>
</div>
</div>
{show && <p className="content">{data}</p>}
</>
);
}
function TestPage() {
return _.map(dummyData, (data, key) => {
return <Data key={key} id={key} data={data} />;
});
}
export default TestPage;

Passing props in map function doesn't work. Passing it as key works

I'm passing an array as props from the App.js to Component B. The array:
myOptions: [
{
id: 1,
option: "Option1"
},
{
id: 2,
option: "Option2"
},
{
id: 3,
option: "Option3"
}
]
From the App.js I'm passing it to Component B:
<ComponentB
options={this.state.myOptions}
/>
ComponentB:
import React from 'react';
import ComponentC from './ComponentC';
function ComponentB (props) {
function renderOptions(key) {
return(
<ComponentC
key={key.option}
optionContent={key.option} //this is my question
answers={props.option}
/>
);
}
return (
<div>
<h3> ComponentB </h3>
{props.options.map(renderOptions)}
</div>
);
}
export default ComponentB;
The ComponentC only displays the optionContent:
import React from 'react';
function ComponentC(props) {
return (
<div>
{props.optionContent}
</div>
);
}
export default ComponentC;
Although the code works correctly, I'm not sure why in the renderOptions function, I need to use key.option to pass the optionContent={key.option} prop. When I use props.option, it doesn't work.
Is it because the renderOptions function is nested in the ComponentB function?
First, you should understand how map() works.
In your code,
function ComponentB (props) {
function renderOptions(key) {
return(
<ComponentC
key={key.option}
optionContent={key.option} //this is my question
answers={props.option}
/>
);
}
return (
<div>
<h3> ComponentB </h3>
{props.options.map(renderOptions)}
</div>
);
}
You use the map() like this {props.options.map(renderOptions)} and it looks okay.
But you should know something about map()'s arguments.
you can read it
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Normally, map() has 3 arguments and be used 2 of them. currentValue and index.
and, return to your code again.
you passed your function renderOptions() itself.
it means renderOptions() can get all arguments from map()
You can touch currentValue, index and other things.
But, renderOptions() has only one argument key.
(If you want to touch all arguments you should write like this function renderOptions(v, i, arr) )
So, you can touch only key as currentValue.
and currentValue has each item of Array.
{
id: 1,
option: "Option1"
}
You have to rewrite your code like that.
function renderOptions(v,i,arr) {
return(
<ComponentC
key={v.id} // because the key should be unique.
optionContent={v.option}
answers={arr} // Actually, I don't understand this purpose.
/>
);
}

When I pass array in a react functional component, it turns to an object

Hi I have to pass array as props to a functional component.
import React from "react";
import { render } from "react-dom";
const App = () => {
const FBS = ({ figures }) => {
console.log(typeof figures);
return figures.map((item, key) => <p key={key}>{item.description}</p>);
};
const figures = [
{
config: 112,
description: "description text 1"
},
{
config: 787,
description: "description text 2"
}
];
return (
<div>
{/* <FBS {...figures} /> */}
<FBS figures={figures} />
</div>
);
};
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>
<body>
<div id='root' />
</body>
But it is converted to an object in the child component.
Please look at the render function. When I pass array as {...figures} I don't get it as Array in the FBS component due to which I can't run map function on it. Whereas when I pass it as figures={figures}, I get an array.
I want to pass it as {...figures}.
Please help
Please look at my code for better understanding. here
You need to have additional object which will have pair of key and value which will be destructured as your props to the React Component.
const props = {
figures, // shorter way of writing figures: figures
// Any other objects you'd like to pass on as props
}
and then, you can do:
<FPS {...props} />
Updated Code
Basically you can only destructure an object in the React Component because then the destructured object's key-value pairs will become props to the component.
For better understanding,
const arr = [{ a: 'a'}]
{...arr}
will give:
{
0: {a: 'a'}
}
because 0 is the key as it is an array as opposed to an object, so what you were really doing was passing a prop with name 0 instead of figures and figures was undefined and hence the error.
You can use something like this:
import React from "react";
import Figure from './Figure';
import { render } from "react-dom";
const App = () => {
const figures = [
{
config: 112,
description: "description text 1"
},
{
config: 787,
description: "description text 2"
}
];
return (
<div>
{
figures.map((figure, key) => {
return <Figure key={key} {...figure}/>
})
}
</div>
);
};
render(<App />, document.getElementById("root"));
And create a component called Figure like this:
import React from "react";
const Figure = (props) => {
return (
<div>
<p>{props.description}</p>
<p>{props.config}</p>
</div>
);
};
export default Figure;

How to arrange React child components order?

With react-semantic-ui, I made a searchable, paginationable Table React Component
working example: https://codesandbox.io/s/23q6vlywy
Usage
<PaginationTable
items={[
{
id: 1,
name: "test-item-1 ???"
},
{
id: 2,
name: "test-item-2"
},
{
id: 3,
name: "test-item-3"
}
]}
columnOption={[
{
header: "idHeader",
cellValue: "id",
onItemClick: item => {
alert(item.id);
},
sortingField: "id"
},
{
header: "Name~",
cellValue: item =>
`*custom text cannot be searched* property can item.name => ${
item.name
} `,
onItemClick: item => {
alert(item.name);
},
sortingField: "name"
}
]}
initSortingField={"id"}
initSortingOrderAsc={false}
pagination
itemsPerPage={2}
searchKeyProperties={["id", "name"]}
/>
But this component currently can only have a certain order
1.SearchBar on top
2.Table on middle
3.PaginationBar on bottom
And actually, I didn't write 3 child components in the Component, SearchBar, Table, PaginationBar
So it's hard for me to rewrite it to render props way to change the order such as below
<PaginationTable {...props}>
{({ SearchBar, Table, PaginationBar })=>
<div>
<SearchBar />
<Table />
<SearchBar />
</PaginationTable>
</div>
</PaginationTable>
Because when I tried to change to render props, I first have to write 3 components independently, which means I have to change all variables under this( this.state, this.props, this.xxxFunction ) to something else.
For example:
In , I can use
<Input onChange={()=>{
this.setState({ searchBarText: e.target.value });
}}/>
But If I change it to 3 components, I have to rewrite it to something like
const SearchBar = ({onTextChange}) => <Input onChange=
{onTextChange}/>
<SearchBar onTextChange={()=>{
this.setState({
searchBarText: e.target.value
});
}} />
Is there any way I can adjust child components order elegantly or is there any way I can write render props easier?
Updated # 2018.10.27 17:36 +08:00
I modified <PaginationTable>'s render function as below but it will be lost mouse cursor focus when typing on <SearchInput>
render(){
const SearchBar = ()= (<Input onChange={()=>{...} />);
const TableElement = ()=>(<Table {...} > ... </Table>);
const PaginationBar = ()=>(<Menu {...} > ... </Menu>);
enter code here
return(
<React.Fragment>
{this.props.children({ SearchBar,TableElement, PaginationBar })}
</React.Fragment>)};
Then I found out, in this way, DOM will update every time when my state updated because every render will make a new component reference by const SearchBar = ()=>(...).
Thanks to #Ralph Najm
If I declare child component as below, DOM will not update every time.
render(){
const SearchBar = (<input onChange={...} />);
const TableElement = (<Table .../>);
const PaginationBar = (<Menu .../>);
//... same as above
}
In this way, I can change my component to render props in order to arrange the order very easily.
Thanks in advance.
In the PaginationTable component change your render function and before the return statement, assign the elements to variables (SearchBar, Table, PaginationBar, etc...) then just reference those variables in the render function.
I modified your example here https://codesandbox.io/s/vy4799zp45

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