Simple react functional component not generating HTML - reactjs

I'm trying to figure out the best ways of creating functional components that generate simple html - take this nav-links.js file for example:
export const navLinks = [
{
name: "home",
href: "/"
},
{
name: "subs",
href: "/subs"
}
];
The html my component attempts to generate just loops through each link to generate an unordered-list with list tags and a tags inside.
The problem occurs in this nav-menu.js file, where the output is just an two <ul></ul> tags with nothing inside:
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import {navLinks} from "../util/nav-links";
export const NavMenu = () => {
const lis = () => (
{navLinks}.map(link => {
return (
<li>
<Link to={link.href} key={link.href}>{link.name}</Link>
</li>
)
})
)
return (
<div id="navbar">
<ul>
{lis}
</ul>
</div>
)
}
Why is nothing rendering in the above component? I wrote some more code below that solves it, although I wonder whether it could be refactored better:
export const NavMenu = () => {
return (
<div id="navbar">
<ul>
{navLinks.map(link => {
return (
<li>
<Link to={link.href} key={link.href}>{link.name}</Link>
</li>
)
})}
</ul>
</div>
)
}
Why does the first attempt not work, and how could it be better refactored? Thanks for any help here

In your the first example didn't work because you passed the const list = () => {..} as a anonymous function without calling it, inside the curly braces in the JSX portion of you code.
If you have called it like {list()}:
return (
<div id="navbar">
<ul>
{lis()}
</ul>
</div>
)
This way it would have probably worked.
Or you could have stored the list inside a variable like this:
const lis = navLinks.map(link => {
return (
<li>
<Link to={link.href} key={link.href}>{link.name}</Link>
</li>
)
}
)
or also like this:
const lis = navLinks.map(link => (
<li>
<Link to={link.href} key={link.href}>{link.name}</Link>
</li>
)
)
)

Related

React: How to display react-icons element using fetch API?

I stored my social links data on firebase, and now I want to make a <li> list of my social links but the icon object I have here, is not showing me the icons instead it's showing me the elements like: <BsBehance />. It should be displayed as icon, how can I do that?
Firebase data:-
Code:-
import { useEffect, useState } from "react";
import * as ReactIcons from "react-icons/fa";
import MainWrapper from "./MainWrapper";
import classes from "./pages.module.css";
export default function Footer() {
const [socialLinks, setSocialLinks] = useState([]);
useEffect(() => {
const fetchSocilLinks = async () => {
const res = await fetch(
"https://mohitdevelops-d64e5-default-rtdb.asia-southeast1.firebasedatabase.app/accounts.json"
);
const data = await res.json();
let loadedData = [];
for (const keys in data) {
loadedData.push({
url: data[keys].url,
icon: data[keys].icon,
id: data[keys].id,
});
}
setSocialLinks(loadedData);
};
fetchSocilLinks().catch((err) => {
console.log(err.message);
});
}, [socialLinks]);
console.log(socialLinks);
return (
<footer className={classes.footer__wrap}>
<MainWrapper>
<p>Connect with me:</p>
<ul className={classes.social_links}>
{socialLinks?.map(({ icon, id, url }) => {
const iconLink = icon.split(/\<|\/>/).filter((e) => e)[0];
const IconsComponent = ReactIcons[iconLink];
return (
<li key={id}>
<a href={url} target="_blank">
<IconsComponent />
</a>
</li>
);
})}
</ul>
</MainWrapper>
</footer>
);
}
And its showing me like this:-
Everything data is coming fine, just need to know how to display an element which is working like an icon.
Since el.icon is a string, it's being displayed as a string. If you want to render the component based on that dynamic string, you need to have an object, where the key name is the string you will get from the server. And the value, can be the component itself.
When you insert a string inside {}, React will render it as a string, even if they're in the < /> format.
And also, try to never use dangerouslySetInnerHTML, since it has high security issues.
Here is a codesanbox, for your above case. Please check this:
You need to use dangerouslySetInnerHTML like this
<ul className={classes.social_links}>
{socialLinks.map((el) => {
return (
<li key={el.id}>
<a href={el.url} target="_blank">
<span dangerouslySetInnerHTML={{__html: el.icon}} />
</a>
</li>
);
})}
</ul>
The icon which need to display should be import from the react-icon/bs and that icon name is coming from the firebase data, so it can be import using dynamic import in react.js but for dynamic import you need to handle loading separately, but I have provided easy way.
Need to install react-icon
npm install react-icons --save
And then import all the Icon
import * as ReactIcons from "react-icons/bs";
And add this line of code
<ul>
{socialLinks?.map(({ icon, id, url }) => {
const iconLink = icon.split(/\<|\/>/).filter((e) => e)[0];
const Compoenent = ReactIcons[iconLink];
return (
<li key={id}>
<a href={url}>
<Compoenent />
</a>
</li>
);
})}
</ul>

Using constants in React Components

I'm trying to print something to console when a list item is clicked. But the list is defined as a constant outside of the component using it. Obviously the onclick/logItem here is not working. How would you go about this?
Strangely, it logs every list item to the console when the component mounts.
My code:
import React, { Component } from 'react';
const LIST_ITEMS = require('./ListItems.json');
const myList =
LIST_ITEMS.data.Items.map((Item) => (
<li key={Item.id} onClick={logItem(Item.name)}>
{Item.name.toUpperCase()}
</li>
))
;
function logItem(name){
console.log(name);
}
class ItemList extends Component {
render() {
return (
<div id="item-list-wrapper">
<h3>Select an Item</h3>
<ul id="item-list">{myList}</ul>
</div>
);
}
}
export default ItemList;
You have to pass a function to onClick, not the result of calling logItem:
onClick={() => logItem(Item.name)}

react-bootstrap breadcrumb with react-router-dom

I'm trying to use react-bootstrap breadcrumb as below.
<Breadcrumb>
<Breadcrumb.Item href="#">Home</Breadcrumb.Item>
<Breadcrumb.Item><Link to={"/products"}>Products</Link></Breadcrumb.Item>
<Breadcrumb.Item active>{productName}</Breadcrumb.Item>
</Breadcrumb>
As you can expect, products Link will render anchor tag inside another anchor tag, which is invalid markup. But Home creates a simple anchor tag instead of react's Link making the page to reload, making it unusable.
What's the solution for this? Unfortunately, there's no mention of this in react-bootstrap doc. (link)
I would probably use react-router-bootstrap, but if you don't want to include it as a dependency, you can apply the link by hand using the now available linkAs and linkProps Breadcrumb params. For instance:
<Breadcrumb.Item linkAs={Link} linkProps={{ to: "/path" }}>
My item
</Breadcrumb.Item>
This approach is interesting especially if you are using just the "as" attribute with other components like Button or NavLink.
This nicely works and it does not refresh the whole page, only what's needed to change
import { Breadcrumb } from "react-bootstrap";
import { Link } from "react-router-dom";
export const SiteMap = ({ hrefIn }) => {
const items = [
{ href: "/dictionaries", name: "Dictionaries" },
{ href: "/antonyms", name: "Antonyms" },
];
return (
<Breadcrumb>
{items.map((item) =>
item.href === hrefIn ? (
<Breadcrumb.Item active>{item.name}</Breadcrumb.Item>
) : (
<Breadcrumb.Item linkProps={{ to: item.href }} linkAs={Link}>
{item.name}
</Breadcrumb.Item>
)
)}
</Breadcrumb>
);
};
I ended up dropping react-boostrap and doing it 'by hand':
const Breadcrumbs = ({ breadcrumbs }) => (
<ol className="breadcrumb">
{breadcrumbs.map((breadcrumb, index) => (
<li key={breadcrumb.key}>
<NavLink to={breadcrumb.props.match.url}>
{breadcrumb}
</NavLink>
</li>
))}
</ol>
);
It works for me if I wrap <Breadcrumb.Item> into the <LinkContainer>.
Now outer Breadcrumb "My applications" which points to /applications URL redirects my app with react-router to the applications page.
I tested this with react-bootstrap v0.32.4 https://5c507d49471426000887a6a7--react-bootstrap.netlify.com/components/breadcrumb/
I got <LinkContainer> from react-router-bootstrap package: https://github.com/react-bootstrap/react-router-bootstrap
I saw "the wrapping" here before, though I don't generate breadcrumb in a loop: https://github.com/react-bootstrap/react-router-bootstrap/issues/141#issue-122688732
import { LinkContainer } from 'react-router-bootstrap';
// and then in the render function
<Breadcrumb>
<LinkContainer to="/applications" exact>
<Breadcrumb.Item>My applications</Breadcrumb.Item>
</LinkContainer>
<Breadcrumb.Item active>My First Applicaton</Breadcrumb.Item>
</Breadcrumb>
Here I have passed onClick to Breadcrumb.Item to handle navigation with the help of
import { withRouter } from "react-router-dom";
Here is the sample:
const RenderBreadcrumb = ({ breadcrumbInfo, history }) => {
const handleRedirect = (url) => {
history.push(url);
}
return (
<Breadcrumb>
{ map(breadcrumbInfo, (item) => {
if(item.isActive) {
return (<Breadcrumb.Item active>{item.text}</Breadcrumb.Item>);
}
return (<Breadcrumb.Item onClick={() => { handleRedirect(item.link); }}>{item.text}</Breadcrumb.Item>);
})}
</Breadcrumb>
)
}
export default withRouter(RenderBreadcrumb);
Bootstrap.Item internally uses SafeAnchor which allows you to not use a link if you don't want to.
Using the as prop you can modify what tag is used (a by default). For example you can pass:
<Bootstrap.Item as="div" />
And it will use a div tag for presenting the item.

React Parent Component Not Rendering Child Component

Basic question, but we all start somewhere.
I have a parent Nav.js
import React, { Component } from 'react'
import navLink from './navLink'
class Nav extends Component {
render () {
return (
<div>
<nav className="bb bt b--black-20 tc center bg-white">
<navLink />
</nav>
</div>
)
}
}
Nav.displayName = 'Nav'
export default Nav
Here's the file that won't get displayed:
navLink.js:
import React from 'react'
import Link from 'next/link'
const navigationLinks = [
{ name: '1', link: '/', router: true },
{ name: '2', link: '#' }
]
const renderLink = (link, i) => {
if (link.router === true) {
return (
<Link href={link.link}>
<a className="yellow" key={i}>{link.name}</a>
</Link>
)
} else {
return (
<a className="red" key={i} target="_blank" rel="noopener noreferrer" href={link.link}>{link.name}</a>
)
}
}
const navLink = () => (
<nav className="container">
{navigationLinks.map(link => renderLink(link))}
</nav>
)
navLink.displayName = 'navLink'
export default navLink
I'm my problem is super basic, but I have no idea how to resolve this. Thanks for your time!
The main problem is navLink with a small letter, it should be NavLink as RenderLink if you want to use it.
In JSX lower case are considered to be a HTML tags, HTML is not have no a navLink tag so it's just didn't displayed.
User-Defined Components Must Be Capitalized
The first part of a JSX tag determines the type of the React element.
Capitalized types indicate that the JSX tag is referring to a React component. These tags get compiled into a direct reference to the named variable, so if you use the JSX expression, Foo must be in scope.
see: docs
Render links
const SimpleLink = ({link}) => (
<Link href={link.link}>
<a className="yellow">{link.name}</a>
</Link>
);
const RenderRouter = ({link}) => (
<a className="red" target="_blank" href={link.link}>{link.name}</a>
);
const renderLink = link => link.router ?
( <SimpleLink key={i} link={link}/> ) :
( <RenderRouter key={i} link={link}/> );
...
const NavLink = () => (
<div className="container">
{ navigationLinks.map((link, i) => renderLink(link, i))}
</div>
);
...
export default NavLink
Import
import NavLink from './navLink.js'
Nav Component
class Nav extends Component {
render () {
return (
<nav className="bb bt b--black-20 tc center bg-white">
<NavLink />
</nav>
)
}
}
See: working example
Fix proptypes
react-router may provide propType definitions for it already, but whether they do or not, you'd still need to explicitly connect them.
Rend.propTypes = {
link: React.PropTypes.shape({
link: React.PropTypes.string.isRequired
}),
};
See: eslint-plugin-react/issues/1109

React - pass object from Container Component to Presentational Component

I'm trying to dynamically add Components (based on ID from an array) into my Presentational Component. I'm new to all this so there is a possibility I'm making it way too difficult for myself.
Here's the code of my Container Component:
class TemplateContentContainer extends Component {
constructor() {
super()
this.fetchModule = this.fetchModule.bind(this)
this.removeModule = this.removeModule.bind(this)
this.renderModule = this.renderModule.bind(this)
}
componentWillReceiveProps(nextProps) {
if(nextProps.addAgain !== this.props.addAgain) // prevent infinite loop
this.fetchModule(nextProps.addedModule)
}
fetchModule(id) {
this.props.dispatch(actions.receiveModule(id))
}
renderModule(moduleId) {
let AddModule = "Modules.module" + moduleId
return <AddModule/>
}
removeModule(moduleRemoved) {
console.log('remove clicked' + moduleRemoved)
this.props.dispatch(actions.removeModule(moduleRemoved))
}
render() {
return (
<div>
<TemplateContent
addedModule={this.props.addedModule}
templateModules={this.props.templateModules}
removeModule={this.removeModule}
renderModule={this.renderModule}
/>
</div>
)
}
}
and the code of the Presentational Component:
const TemplateContent = (props) => {
let templateModules = props.templateModules.map((module, index) => (
<li key={index}>
{props.renderModule(module)}
<button onClick={props.removeModule.bind(this, index)}>
remove
</button>
</li>
))
return (
<div>
<ul>
{templateModules}
</ul>
</div>
)
}
the renderModule function returns object, but when it's being passed to the presentational Component it doesn't work anymore (unless it's passed as className for example then it returns object)
I'm importing the modules from modules folder where I export them all into index.js file
import * as Modules from '../components/modules'
Hope it makes sense, any help would be highly appreciated.
Thanks a lot in advance!
I would recommend to restructure the files to make for an easier handling.
If your container components render would look like this:
render () {
return (
<div>
<ul>
{this.props.templateModules.map(module => (
<ChildComponent onRemove={this.removeModule} module={module} />
)}
</ul>
</div>
)
}
Your child component can just handle the remove click and the displaying of the module data
EDIT:
My bad, I just misunderstood your problem.
I would map the ids to the according components instead of concatenating the name of the Component you want to render, so your container component would look something like this:
getChildComponent (id) {
const foo = {
foo: () => {
return <Foo onRemove={this.removeModule} />
},
bar: () => {
return <Bar onRemove={this.removeModule} />
}
}
return foo[id]
}
render () {
return (
<div>
<ul>
{this.props.templateModules.map(module => (
{this.getChildComponent(module.id)()}
)}
</ul>
</div>
)
}
Also you should maybe have a look at react-redux and move your dispatches to react-redux containers.

Resources