How to pass a react component as a variable, to child component? - reactjs

When I have defined a component in a variable, and I am trying to pass it to a component as a children prop, Objects are not valid as a React child error is shown.
What is a correct way to do this?
function AnotherComponent(){
return "Another";
}
function ChildComponent(props) {
const { children, value, index, ...other } = props;
console.log(children);
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`full-width-tabpanel-${index}`}
aria-labelledby={`full-width-tab-${index}`}
{...other}
>
<Box p={3}>{children}</Box>
</Typography>
);
}
function MainComponent(){
const tabItems = [
{ "component": AnotherComponent}
];
const [value, setValue] = React.useState(0);
return (
<>
{tabItems.map((tabItem,index) => (
<ChildComponent value={value} index={tabItem.index}>
{tabItem.component}
</ChildComponent>
))}
</>
)
}

tabItem.component is just an object. This should work :
{tabItems.map((tabItem, index) => {
const TheComponent = tabItem.component;
return (
<TabPanel value={value} index={tabItem.index}>
<TheComponent />
</TabPanel>
);
})}

The problem is the way you initialise the array is really just assigning a function into it. Exactly as error says - you cant render a function. You have to wrap that function into JSX syntax to do the whole React.createChild thingy.
So just change this line
const tabItems = [
{ "component": AnotherComponent}
];
to this:
const tabItems = [
{ "component": <AnotherComponent />}
];
and it will work like a charm. :)

Related

JSON strings not displaying in JSX

For some reason, I just can't get JSON strings to display in my component. I've tried various solutions, but no luck.
Below is my component where I map over my items:
UnitEventsItems.js
const GetEventsItems = (props) => {
const { parsed = {} } = props;
const getEventsItems = Object.entries(parsed).map((item, i) => ({
item_Id: i,
label: item[0],
info: item[1]
}));
return (
<div style={{
marginBottom: theme.spacing(4)
}}
>
{getEventsItems.map((eventsItems) => (
...
<Typography>
{`${eventsItems.info}`}
</Typography>
</Box>
Below is the component where I import getEventItems for display:
EventsBlock.js
import GetEventsItems from './UnitEventsItems';
...
const EventsBlock = (props) => {
const classes = useStyles(props);
const {
eventsData,
type
} = props;
return (
<>
{
...
<Paper className={clsx(classes.paper, classes.scrollBar)}>
<List component="nav" aria-label={`${type} Events`}>
{eventsData.map((item, i) => (
<ListItem key={`${i + 1}${item.type}_${item.trixid}`}>
<GetEventsItems
parsed={item.comment}
/>
</ListItem>
))}
</List>
</Paper>
And then this is the parent where I import EventBlock.js and pass the props:
index.js
import React from 'react';
import EventsBlock from './EventsBlock';
const UnitEvents = (props) => {
const { eventsData } = props;
const refueling = (eventsData?.Refueling ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
const periodic = (eventsData?.Periodic ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
const corrective = (eventsData?.Corrective ?? []).map((event) => (event?.comment ? JSON.parse(event.comment) : {}));
console.log('periodic:', periodic);
console.log('refueling:', refueling);
console.log('corrective:', corrective);
return (
<>
<EventsBlock type="Periodic" eventsData={periodic} />
<EventsBlock type="Refueling" eventsData={refueling} />
<EventsBlock type="Corrective" eventsData={corrective} />
</>
);
};
export default UnitEvents;
Below is an image of what data looks like coming through from the API:
Below is what I have logged in the console as shown in the code above:
This is live example of code Sandbox
What is wrong on your code
In the EventsBlock component you need to have a check Object.keys(eventsData).length after that Draw eventsData.map
Inside of eventsData.map you need also some check before draw GetEventsItems , something like
{ item.comment && (
<li>
<GetEventsItems parsed={item.comment } />
</li>
)
Check this example , I have wrote your logic with other json data , try to use that aproach with your json data. code
Problem solved by making the following the change.
Changed this:
<GetEventsItems parsed={item.comment} />
to this:
<GetEventsItems parsed={item}/>

Render React Component from name inside array map

This is either very simple or I am doing it completely wrong. I am a novice so please advise.
I am trying to show different components inside different tabs using Material UI using array map. The tabs are showing fine but the components do not render. Basically if the array label is 'Welcome', the tab name should be 'Welcome' and the Welcome component should show up and so on. Please help!
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
{fetchedCategories.map((category) => (
<Tab key={category.label} label={category.label} />
))}
</Tabs>
</Box>
{fetchedCategories.map((category, index) => {
const Component=myComponents[category.label];
})}
{fetchedCategories.map((category, index) => (
<TabPanel key={category.label} value={value} index={index}>
<Component label={category.label} />
</TabPanel>
))}
</Box>
);
Here is my props & Component function:
interface ComponentProps {
label: string;
value?: number;
}
function Component (props: ComponentProps)
{
const {label, value} = props;
return myComponents[label];
}
const myComponents = {
'Welcome': Welcome,
'Salad/Soup': Welcome
}
Try something like:
function Component({ label, value }: ComponentProps) {
const [Comp, setComponent] = useState(<div />);
React.useEffect(() => {
const LabelComp = myComponents[label];
if (label && LabelComp) {
setComponent(<LabelComp value={value} />); // <-- if you want to pass value to you component
}
}, [value, label]);
return Comp;
}
And you will use it like:
const App = () => {
return <Component label={"myComponentLabel"} value={"some value"} />;
};

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

Passing array of object into functional component and mapping

I'm trying to use Material UI to create a reusable navigation tab, however, I am having trouble passing the object over to my functional component and mapping it out. Nothing displays when mapping.
I am fairly new to react hooks. Thanks in advance.
Class Component (passing state over to Navigation)
class MyWorkspace extends Component {
state = {
menuItem: [
{
name: "menu 01",
urlPath: "/home/menu01"
},
{
name: "menu 02",
urlPath: "/home/menu02"
},
{
name: "Reports",
urlPath: "/home/menu03"
},
],
}
}
render () {
return (
<div>
<Navigation menuItem />
</div>
)
}
Functional Component
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
<MenuList />
</Tabs>
</div>
);
}
In the class component, you should assign a value to the prop being passed:
render () {
return (
<div>
<Navigation menuItem={this.state.menuItem} />
</div>
)
}
In function component, you should call MenuList() inside the render :
export default function Navigation({ menuItem }) {
const [value, setValue] = React.useState(2);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const MenuList = () => {
return (
<>
{menuItem.map(item => {
return <Tab label={item.name} className="Nav-Tab" />;
})}
</>
)
}
return (
<div className="Nav-Title row">
<Tabs
className="Nav-Tab-List"
value={value}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}
>
{MenuList()} // call this or put the map here
</Tabs>
</div>
);
}
First you need to define state in constructor
Second destruct menuitem from state
const {menuItem} = this.state
Third pass props like this
<Navigation menuItem={menuItem} />
If you pass like this Navigation menuItem /> you will get boolean value true inside child component.
In MyWorkspace/render function, you don't actually pass the menuItem state.
<Navigation menuItem /> will pass menuItem as true value. Replace it with: <Navigation menuItem={this.state.menuItem} />
Navigation component code looks correct

Trying to conditional render css to on alternating items in an array with useState in React with MUI

I have an object of arrays that I'm mapping over called featuredProj, and on alternate items, I want the CSS to conditionally render as not to repeat so much code. Using useState in the handleSide function I get the error too many rerenders. How can I solve this, or is there a better solution to rendering jsx while mapping over an array of objects.
const useStyles = makeStyles(() => ({
title: {
textAlign: (side) => (side ? "right" : "left"),
},
}));
const FeaturedProjects = () => {
const [side, setSide] = useState(true);
const classes = useStyles(side);
const handleSide = (project, index) => {
if (index === 0 || index % 2 === 0) {
// I tried setSide(false), setSide(prev => !prev)
return (
<Grid Container key={index}>
<Typography className={classes.title}>{project.title}</Typography>
</Grid>
);
} else {
return (
<Grid Container key={index}>
<Typography className={classes.title}>{project.title}</Typography>
</Grid>
);
}
};
return (
<Container>
{featuredProj.map((proj, ind) => (
<Reveal duration="2000" effect="fadeInUp">
{handleSide(proj, ind)}
</Reveal>
))}
</Container>
);
};
Thanks in advance for any assistance!
You cannot call setState() inside render method. setState() will trigger a re-rendering which calls render method again which leads to another setState().. you get the idea.
If you want it to work, you need to create separate component with the side props and pass it as an argument to your style hook.
const FeaturedProjects = () => {
const classes = useStyles(side);
return (
<Container>
{featuredProj.map((proj, ind) => (
<FeaturedProjectItem
key={index}
project={proj}
side={index === 0 || index % 2 === 0}
/>
))}
</Container>
);
};
const useStyles = makeStyles({
title: {
textAlign: (side) => (side ? "right" : "left"),
},
});
const FeaturedProjectItem = ({ side, project }) => {
const classes = useStyles(side);
return (
<Reveal duration="2000" effect="fadeInUp">
<Grid Container>
<Typography className={classes.title}>{project.title}</Typography>
</Grid>
</Reveal>
);
};

Resources