How to render the component from property of object? - javascript

I am building a navbar which contains few icons and titles.
To build it, I am using react-icons (https://www.npmjs.com/package/react-icons).
So, I am importing these items here
import { FaMobileAlt, FaCreditCard, FaRegBuilding } from 'react-icons/fa';
and I have a const responsible for keep the menu items list
const LEFT_MENU_ITEMS = [
{ key: 'devices', icon: FaMobileAlt, title: 'Devices' },
{ key: 'cards', icon: FaCreditCard, title: 'Cards' },
{ key: 'business', icon: FaRegBuilding, title: 'Business' },
];
The normal usage would be something like <FaMobileAlt /> and thats it, but in my case I am trying to iterate over this const to build my list.
buildLeftMenuBar() {
if (!this.props.loggedIn) return null;
return (
<ul key="leftMenuBar" className="items">
{_.map(LEFT_MENU_ITEMS, itemDef => (
<li key={itemDef.key}>
<NavLink to={`/${itemDef.key}`}>
<div>
>>>>> {itemDef.icon} <<<<
</div>
<span>{itemDef.title}</span>
</NavLink>
</li>
))}
</ul>
);
}
The error I get when render the page is
Functions are not valid as a React child. This may happen if you return a Component instead of from render.
So, each icon isnt being rendered. How can I make it work following the same idea ?
ps: It's not only 3 items, I just removed some to make it easier to read the question, thats why I am trying to iterate.

You may use like this:
<itemDef.icon />
Instead of this:
{itemDef.icon}
Or, you may also use like:
{itemDef.icon()}
You might be wondering what's going in here. So, let me explain a little bit:
When you want to render a component say MyComponent, you will be able to print like:
{MyComponent()}
Or,
<MyComponent />
But not like:
{MyComponent}
Because, you will need to call the function. That's it.
As per your comment, you want to supply the size props in the component as you said:
<itemDef.icon size={20} />
And {itemDef.icon()} is not just limited. You can also pass the props here:
{itemDef.icon({size:20})} // component receives size props 20
Though, I would recommend to use <itemDef.icon size={20} /> because it's little bit clear usage.

You can do it like this:
return(
<ul key="leftMenuBar" className="items">
{_.map(LEFT_MENU_ITEMS, item => {
const Icon = item.icon;
return (
<li key={itemDef.key}>
<NavLink to={`/${itemDef.key}`}>
<Icon />
<span>{itemDef.title}</span>
</NavLink>
</li>
);
})}
</ul>
);

Related

React: Data within a map is appearing different even though the code is written the same way for both instances where used

Apologies if this is poorly written (first time posting here so feedback on how to better write posts welcome!)
I am using react map to iterate through lists of data.
{level1Folders.level2Folders.map(
(level2Folders, index) => {
return (
<li
id={level2Folders.folderLevel2Name}
key={level2Folders.folderLevel2Name + index}
>
<div
className="menu-item-folder-level-2"
onClick={() =>
hideMenuItem(
level2Folders.folderLevel2Name
)
}
>
<FaIcons.FaCaretRight />
{level2Folders.folderLevel2Name}
</div>
<ul
className="manuals d-none"
id={level2Folders.folderLevel2Name}
>
{level2Folders.manuals.map(
(manual, index) => {
return (
<li key={manual + index} id={manual}>
<div
onClick={() =>
handleExplorerItemClick(manual)
}
className="menu-item-manual"
>
{manual}
</div>
</li>
);
}
)}
I have a method hideMenuItem(menuItemId) which will hide items based on their id's, so the idea is to set the id = to the name of the item, so when the parent item is clicked the child elements will be hidden.
function hideMenuItem(menuItemId) {
console.log(menuItemId);
let x = document.getElementById(menuItemId);
if (x.classList.contains('d-block')) {
x.classList.add('d-none');
x.classList.remove('d-block');
} else {
x.classList.add('d-block');
x.classList.remove('d-none');
}
}
I have 5 uses of this - level2Folders.folderLevel2Name, the only one that won't work is when trying to enter this as a parameter in hideMenuItem(menuItemId), the value here is returned as the index of the item.
The point here is you want to toggle the item's child by using the element's classlist. It might be better if you change your approach and use react ways to achieve your goals. One of them is conditional styling where you can read here for the details.
For your case, let me show you one of many approach which using state in show and hide elements. Try absorp the concept and implement it at yours.
First, the data should have name which is the name of folder, showSubFolders which is property that will keep the boolean value, and subFolders that contains the sub folders details.
const folderData = [
{
name: "My Documents",
showSubFolders: false,
subFolders: [
{
name: "My Music",
icon: faMusic
},
{
name: "My Images",
icon: faImage
}
]
},
...
}
Then, set a folders state that will keep the folder's data in our component:
export default function OurComponent() {
const [folders, setFolders] = useState(folderData);
...
}
Since we use <FontAwesomeIcon icon={...} />, so we can render the main folder icon by using conditional icon selection which depends on folder's showSubFolders property value:
return (
...
<ul>
{folders.map((folder, index) => {
return (
<li>
<FontAwesomeIcon
icon={folder.showSubFolders ? faFolderOpen : faFolder}
/>
</li>
...
)
})}
</ul>
...
)
The next is toggle subFolders section by creating a toggleFolder method that use useCallback hooks and depends on folders state. We negate the value of showSubFolders property if the folder name equal to the argument name supplied.
const toggleFolder = useCallback(
(name) => {
const toggledFolders = folders.map((folder) => {
if (folder.name === name) {
folder.showSubFolders = !folder.showSubFolders;
}
return folder;
});
setFolders(toggledFolders);
},
[folders]
);
And we can call the toggleFolder method from our list item as follow:
return (
...
{folders.map((folder, index) => {
return (
<li key={index} style={{ cursor: "pointer" }}>
<span onClick={() => toggleFolder(folder.name)}>
<FontAwesomeIcon
icon={folder.showSubFolders ? faFolderOpen : faFolder}
/>
{folder.name}
</span>
...
</li>
...
)
})}
...
)
And when the folders state change, the component will re-render and we can use conditional render technique {folder.showSubFolders && ( ... ) here:
<li>
...
{folder.showSubFolders && (
<ul>
{folder.subFolders.map((subFolder, index) => {
return (
<li key={index}>
<FontAwesomeIcon icon={subFolder.icon} />
{subFolder.name}
</li>
);
})}
</ul>
)}
</li>
Of course, this is not the only way to achieve your goal, but it is more React Way in doing so.
And lastly, this is the final code:

React: Child Components Failed to Receive Multiple Mapped Data Using Props

I'm new to react. I want to create a simple react web app that receives data from 2 api, and display the data.
I have three components. App.js receive data from two api, and pass the data to FruitGrid.js using hooks and props. FruitGrid.js map the two data received, and pass to FruitItem.
My problem is in FruitGrid.js. Data is received in FruitGrid.js, I can see by console log or print using html tags. But when I try to send the mapped data to FruitItem.js by changing the h1 tag to <FruitItem></FruitItem>, I can only successfully pass one data, not both of them.
FruitGrid.js
import React from 'react';
import FruitItem from './FruitItem';
const FruitGrid = ({ items, images, isLoading }) => {
return isLoading ? (
<h1>Loading...</h1>
) : (
<section>
{items.map((item) => {
return <FruitItem key={item.id} item={item}></FruitItem>
})}
{images.map(image => {
return <FruitItem key={image.id} image={image}></FruitItem>
// return <h1>{image}</h1>
})}
</section>
)
}
export default FruitGrid;
If I only do return <FruitItem key={item.id} item={item}></FruitItem> the item data will show in the correct layout, image won't show since I didn't pass. But if I try to pass both item and image using <FruitItem></FruitItem>. It will show error saying "TypeError: Cannot read property 'name' of undefined" in FruitItem.
FruitItem.js
import React from 'react'
const FruitItem = ({ item, image }) => {
return (
<div className='card'>
<div className='card-inner'>
<div className='card-front'>
<img src={image} alt='' />
</div>
<div className='card-back'>
<h1>{item.name}</h1>
<ul>
</ul>
</div>
</div>
</div>
)
}
export default FruitItem
Can someone help and let me know how i can fix it?
Hey #yywhocodes this is happening because the FruitItem.js is always expecting an item object and your code for the images mapping is not providing the item object -> return <FruitItem key={image.id} image={image}></FruitItem>
What you can do is change the FruitItem.js
from
<h1>{item.name}</h1>
to
{ item ? <h1>{item.name}</h1> : null }
like that it will try to render the item.name only if the item exists.
You are iterating over the two data sources separately, so items are not defined in your images.map and images are not defined in your items.map
In order to combine, assuming the order of the data is the same/the keys are the same in both cases, you could do something like this:
{items.map((item, key) => {
return <FruitItem key={item.id} item={item} image={images[key]}></FruitItem>
})}
You need to pass props key={item.id} item={item} to the child FruitItem component. If you can't pass any props to the FruitItem component, react won't figure out the item.name. Which will be a TypeError.
It's because your FruitItem always expects an item to be handed in
<h1>{item.name}</h1>
But when you hand in your image, there is no item you set.

Key prop exists on list item but still getting key warning

I've seen all flavors of this issue and yet I can't seem to figure out what on earth is going wrong. I do not extract out my list items, I am just displaying a UL and I gave a unique key to each list item. When I console log out results, I get something like:
0:
$$typeof: Symbol(react.element)
key: "85110d30-0a52-4b95-bae2-e98a8a7e4e2c"
In my parent component:
<Route path='/projects/'>
{ isLoading ? (
<div>
<p>Loading...</p>
</div>
) : (
[
<ProjectList projects={this.state.userProjects} />,
<CreateProject handler={this.handleNewProject} user={user} />
]
)}
In ProjectList component:
const results = props.projects.map( (result) =>
<li key={result.MembershipId}>
<Link to={ `${match.url}/${result.ProjectId}` }>{result.ProjectName}</Link>
</li>
);
return (
<React.Fragment>
<h2>My Projects</h2>
{ results.length ? (
<ul>{results}</ul>
) : (
<p>You are not part of any projects yet. You can create one!</p>
)}
</React.Fragment>
);
the li is the outermost element if I understand correctly. The ID is not undefined. Yet I still keep getting key warnings. Anyone have any idea what is going on?
Is this due to the fact you are returning an array as children in the route without adding keys?

JSX get Component by string

This is pretty complicated to explain.
I am getting a couple of Icons from a component library as follows:
import { Icons } frommy-component-library';`
Let say I've got 3 Icons in there called IconPlus, IconMinus and IconEquals.
I can easily display the IconEquals along with a description prop as follows:
const IconContainer = ({ description ) => (
<div>
{description}
<Icons.IconEquals />
</div>
)
This works nicely. Now I'm trying to implement a template where I could pass another prop icon to this container which would display the corresponding icon.
Eg. if icon is IconPlus
Then it should render the following:
<div>
{description}
<Icons.IconEquals />
</div>
How do I implement my JSX to do that?
This is basically what I've got:
import { Icons } from 'my-component-library'
const IconContainer = ({ description, icon }) => (
<div>
{description}
<Icons.{icon} /> // <---- Obviously that doesn't work
// ^^^^ But I need something like this
</div>
);
Is it possible to do this?
You can use like this :
<Icons[icon] />

React props.children is not array

According to the react docs, if a component has multiple children, this.props.children should be an array.
I have the following component:
export class Two extends React.Component {
componentDidMount() {
console.log(Array.isArray(this.props.children)); // false
}
render() {
return(
<div>
{this.props.children}
</div>
);
}
};
Which I pass children to in another component's render() method:
<Two>
<Img src="/photos/tomato.jpg"/>
<Img src="/photos/tomato.jpg"/>
</Two>
Why is this.props.children not an array? More importantly, how can I get it to be one?
Found a better solution to this after some digging in the React.Children source. It looks like a .toArray() method has been added in React 0.14, soon to be released.
Once it is out we will be able to simply do something like this:
let children = React.Children.toArray(this.props.children);
It's documented in https://reactjs.org/docs/react-api.html#reactchildren
I found this solution. It will render all children, one or more.
const BigMama = ({ children, styles, className }) => {
return (
<div
styles={{styles}}
className={(className ? className : '')}
>
{
React.Children.map(children, (child) =>
<React.Fragment>{child}</React.Fragment>)
}
</div>)
}
<BigMama
styles={{border: 'solid groove'}}
className='bass-player'
>
<h1>Foo</h1>
<h2>Bar</h2>
<h3>Baz</h3>
<h4>Impossibru!</h4>
<BigMama>
If you want to do something with the results (eg. render the array in a list), this is how you can use children. The React docs talk about how the map function works with children, as it's an opaque structure.
export const List = ({ children }) => {
return (
<ul>
{React.Children.map(children, child => <li>{child}</li>)}
</ul>
)
}
So then you can call
<List>
{item.name}
{item.price}
</List>
There are many ways and some are mentioned above already but if you wanna keep it simple and want to achieve this without any utility then below should work
const content = [
<Img src="/photos/tomato.jpg"/>,
<Img src="/photos/tomato.jpg"/>
];
<Two>
{content}
</Two>
You might find the spread syntax useful in this case. Have you tried it out?
<div>
{ [...this.props.children] }
</div>
Combine with map to manipulate the output.
<div>
{ [...this.props.children].map(obj => <div style="someStyling"> {obj} </div> ) }
</div>
Is it because is it a DOM node? Try console.log(this.props.children) you will notice that it logged a array of objects (note that each object contains information of the child element of the component). I read that node and array are not the same tho they have a same format. Visit Javascript DOMNode List for more information.

Categories