Importing FontAwesome icons by string array in React.js - javascript

I'm having an array of sidebar elements in my React.js project where each element is represented as object which among others has its own FontAwesome icon defined as string, like e.g. fa-phone. Now there's a problem with FontAwesome's integration into React.js; each icon has to be separately imported and added to the library, according to their manual.
import * as React from 'react';
import { library } from '#fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
interface SidebarElement {
fa: string,
href: string,
title: string
}
interface SidebarElements {
elements: SidebarElement[]
}
export default class Sidebar extends React.Component<SidebarElements, {}> {
render() {
const elements = this.props.elements.map((element, key) => {
// tweak icon name so it matches component name...?
...
// the two lines below obviously won't work
import { element.fa } from '#fortawesome/free-solid-svg-icons'
library.add(element.fa);
return (
<li key={key} className="nav-item">
<a className="nav-link" href={element.href}>
<FontAwesomeIcon icon={element.fa} />
<span>{element.title}</span>
</a>
</li>
);
})
return (
<ul className="sidebar navbar-nav">{elements}</ul>
);
}
}
But the solution above obviously won't work, since imports have to happen at top-level and won't take the component name from a variable. Are there any alternative ways to import icons without exactly knowing them from the beginning? Or am I forced to import all icons at the same point I'm defining my sidebar elements?

I went with this same issue on a personal project I'm building. The first problem I found was related to how dynamically rendering the icon from a query?
Main app container:
import React from "react"
import Header from "../components/header"
import Navigation from "../components/navigation"
import Footer from "../components/footer"
import containerStyles from "./styles.module.less"
import { library } from "#fortawesome/fontawesome-svg-core"
import { fab } from "#fortawesome/free-brands-svg-icons"
library.add(fab)
const IndexPage = ({ data }) => (
<div className={containerStyles.home}>
<div className={containerStyles.menu}>
<Header />
<Navigation />
</div>
<Footer />
</div>
)
export default IndexPage
Also, my icons are part of the free-brand version so imported them to the library.
So the first thing I did was to import the library and create a pair of null variables on my child component, one for the prefix and the other one for the icon itself:
In my project, I'm consuming the data from an API endpoint, the query I built to get the information is the following:
Theoretically, all was set just for mapping the array and render each item as we normally do:
<FontAwesomeIcon
icon={[
(faprefix = document.node.prefix),
(faicon = document.node.icon),
]}
size="lg"
/>
But the child component was rendering nothing. Why was this? Simply because of both document.node.prefix and document.node.icon are returning strings so when the component mapped the data from the array, it ended trying to render something like this:
<svg data-prefix="'fab'" data-icon="'github'" >
Please note the single quotation mark wrapping the string
My solution to this was to use a replace() method with a regex to remove the wrapping quotations marks:
<FontAwesomeIcon
icon={[
(faprefix = document.node.prefix.replace(/'/g, "")),
(faicon = document.node.icon.replace(/'/g, "")),
]}
size="lg"
/>
Child footer component
import React from "react"
import { StaticQuery, graphql } from "gatsby"
import containerStyles from "../pages/styles.module.less"
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
let faicon = null
let faprefix = null
const Navigation = ({ data }) => (
<StaticQuery
query={graphql`
query FooterTemplate {
allStrapiLink {
edges {
node {
id
icon
url
prefix
}
}
}
}
`}
render={data => (
<>
<footer>
<p>Freddy Polanía {new Date().getFullYear()}</p>
<div className={containerStyles.links}>
{data.allStrapiLink.edges.map(document => (
<div key={document.node.id}>
<a
href={document.node.url}
rel="noopener noreferrer"
target="_blank"
>
<FontAwesomeIcon
icon={[
(faprefix = document.node.prefix.replace(/'/g, "")),
(faicon = document.node.icon.replace(/'/g, "")),
]}
size="lg"
/>
</a>
</div>
))}
</div>
</footer>
</>
)}
/>
)
export default Navigation
Now my icons are rendering from the endpoint's data. I hope this could help to solve your issue.

Related

How to render different style css on same component?

I have react component which is a button and I render this component three times. I want to add some CSS on the second component but I don't know how. I tried to add some class names, but then I can't figure it out where to put this style in the CSS.
I can change css in element.style in dev tools but can't in project.
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
return (
<div>
<div className='divBut'>
<NavBar></NavBar>, <NavBar className='drugi'></NavBar>,<NavBar></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
import styled from 'styled-components';
const NavBar = (props) => {
return (
<div>
<Container>
<a class='neon'>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
I try to add props to component
<!-- begin snippet: js hide: false console: true babel: false -->
and then add a type to a component like this
const NavBar = (type) => {
return (
<div>
<Container>
<a class={`neon ${type}`}>Neon</a>
</Container>
</div>
);
};
<NavBar></NavBar>, <NavBar type='drugi'></NavBar>,<NavBar></NavBar>
but nothing is change.
You have props that you don't use, this is a good simple read on How to Pass Props to component, you can adjust this to other needs, this is example...:
import styled from 'styled-components';
const NavBar = ({class}) => {
return (
<div>
<Container>
<a class={class}>Neon</a>
</Container>
</div>
);
};
const Container = styled.div`
background-color: transparent;
`;
export default NavBar;
...
import './App.css';
import './flow.css';
import './neonButton.css';
import GlowBox from './GlowBox';
import NavBar from './NavBar';
function App() {
const NavStyles = {
className1: 'neon',
className2: 'drugi'
};
return (
<div>
<div className='divBut'>
<NavBar class={NavStyles.className1}></NavBar>, <NavBar class={NavStyles.className2}></NavBar>,<NavBar class={NavStyles.className1}></NavBar>
</div>
<GlowBox></GlowBox>
</div>
);
}
export default App;
Edit: Given that you have edited your question I have new information for you.
1.) You can't use the reserved word class in React, because class means something different in Javascript than it does in html. You need to replace all instances of class with className.
2.) Did you notice how in the devtools on your button it says the className says: neon [object object]?
You should use a ternary operator to handle the cases where you don't pass the type prop.
ex.) class={neon ${props?.type !== undefined ? type ''}}
3.) You are trying to apply a className to a component, which does not work. The className attribute can only be applied directly to JSX tags like h1, div, etc. Use a different prop name, then you can use that to decide the elements className.

How to apply css for a class in react component?

I am a new react-js learner and I am having a hard time adding css to my classes that I have inside my react component.
Here is the current code:
import React from 'react';
const Home = () => {
return (
<>
<div class="container">
<h1 class="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
.container {
// CSS would go here
}
export default Home;
In just HTML and CSS, I was able to apply css on the container div class by just using '.' and whatever the class name was. However, this is giving me an error.
Put the css in its own file, with a .css extension, then import it. Assuming you used create-react-app to set up your project, it will already have appropriate configuration for importing css files. Additionally, you need to use className for the prop, not class
// In a new file home.css:
.container {
// css goes here
}
// In the file you've shown:
import React from 'react';
import './home.css';
const Home = () => {
return (
<>
<div className="container">
<h1 className="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
export default Home;
Or you can declare it in json format or like you would an object, not in CSS form. Treat it as you are writing in js, which you actually are. See the edit below:
import React from 'react';
const Home = () => {
return (
<>
<div style={container}>
<h1 className="mainHeader">Home</h1>
<h2>helloo</h2>
</div>
</>
);
};
const container = {
// CSS would go here
color: 'red',
background: 'blue'
}
export default Home;

Reactjs, Passing data(id) from one component to another in order to download a song

Hi I am new to Reactjs and I'm practicing on a project of a music website. But there is an issue which I'm stuck with.
Well there is a song page where I should show some songCards.
import React from 'react'
import SongCard from '../components/SongCard';
import { songsList } from '../components/songsList';
//this is first object of the songsList file which i hard coded instead of using api:
export const songsList =[
{
id : 1,
url : './components/Player/Songs/Homayoun Shajarian - Souvashoun.mp3',
image : require('../assets/Souvashoun.jfif').default,
name : 'x',
singer : 'y',
style :'z',
lyrics :'v',
source : './Palyer/Songs/Homayoun Shajarian - Souvashoun.mp3'
}];
export default function Songs() {
return(
<div>
<SongCard className='col-md-6' id={songsList[0].id} image={songsList[0].image} name={songsList[0].name} singer={songsList[0].singer}/><br/>
<SongCard className='col-md-6' id={songsList[1].id} image={songsList[1].image} name={songsList[1].name} singer={songsList[1].singer}/><br/>
<SongCard className='col-md-6' id={songsList[2].id} image={songsList[2].image} name={songsList[2].name} singer={songsList[2].singer}/><br/>
</div>
);
}
and in the SongCard which I'll attach the code bellow, there is a button intended to route us to a downloadMusic page in order to download the specific song that it's button was clicked:
import React from 'react'
import {Card, Button} from 'react-bootstrap';
import styled from 'styled-components';
import {BrowserRouter as Router} from 'react-router-dom';
function SongCard(props){
const id = this.props.id;
this.props.history.push({
pathname: '/downloadMusic',
search: '?query=downloadMusic',
state: { data: id }
})
return(
<Styles>
<Router>
<Card style={{ width: '18rem'}} className="cardbg shadow" text="white">
<Card.Img variant="top" src={props.image} className="img-fluid" />
<Card.Body className="text-center">
<Card.Title>آهنگ {props.name}</Card.Title>
<Card.Text>خواننده {props.singer}
</Card.Text>
<Button href="/DownloadMusic/:id" className="btn btn-outline-dark">go to download page</Button>
</Card.Body>
</Card>
</Router>
</Styles>
);
}
export default SongCard;
And finally this is the last page which is downloadMusic. Here also there should be a card containing information about the specific song and the final download button.
import React from 'react';
import { songsList } from './songsList';
import DownloadCard from './DownloadCard';
import AudioPlayer from './Player/AudioPlayer';
function DownloadMusic() {
const id = this.props.location.state.data;
const song = songsList.find(item => item.id === id);
return(
<div>
<DownloadCard className='col-md-6' id= {song.id} image={song.image} name={song.name} singer={song.singer}/><br/>
<AudioPlayer/>
</div>
);
}
export default DownloadMusic;
What I want is to send the id and url of that specific song that was clicked in the Song page, to downloadMusic page and show it's information from songsList in a card. I've tried so many ways like using state, useParams(), ... but none of them worked for me. Also I don't want to send the whole props because I know it's not the best practice. So what am I doing wrong?
Hope I've shared enough information. any help is appreciated.
what you have to do is map the array of songs, like this
import React from 'react'
import React from 'react'
import SongCard from '../components/SongCard';
import { songsList } from '../components/songsList';
//this is first object of the songsList file which i hard coded instead of using api:
const songsList =[
{
id : 1,
url : './components/Player/Songs/Homayoun Shajarian - Souvashoun.mp3',
image : require('../assets/Souvashoun.jfif').default,
name : 'x',
singer : 'y',
style :'z',
lyrics :'v',
source : './Palyer/Songs/Homayoun Shajarian - Souvashoun.mp3'
}];
export default function Songs() {
return(
<div>
{
songsList.map((song, idx) => {
return <SongCard key={idx} className='col-md-6' id={song.id} image={song.image} name={song.name} singer={song.singer}/>
})
}
</div>
);
}
Alright I finally found a way to do this, after a long effort! I'm gonna share it here if someone needed.
So in the SongCard.js I used useHistory() and a navigate function to save the id and navigate to downloadMusic page also in the onClick attribute of button used navigate function:
import React from 'react'
import {Card, Button} from 'react-bootstrap';
import styled from 'styled-components';
import {BrowserRouter as Router, useHistory} from 'react-router-dom';
function SongCard(props){
//navigating and saving id
const history = useHistory()
const navigate = (id) => {
history.push(`/downloadMusic/${id}`)
}
return(
<Styles>
<Router>
<Card style={{ width: '18rem'}} className="cardbg shadow" text="white">
<Card.Img variant="top" src={props.image} className="img-fluid" />
<Card.Body className="text-center">
<Card.Title>آهنگ {props.name}</Card.Title>
<Card.Text>خواننده {props.singer}
</Card.Text>
//make sure you add onClick attribute!
<Button onClick={() => navigate(props.id)} className="btn btn-outline-dark">download</Button>
</Card.Body>
</Card>
</Router>
</Styles>
);
}
export default SongCard;
Then in the DownloadMusic.js by using useRouteMatch() I accessed id param, this id was string so I converted it to number in the next step. At the end by using .find() method I found the specific song that was clicked for!
import React from 'react';
import { songsList } from './songsList';
import DownloadCard from './DownloadCard';
import AudioPlayer from './Player/AudioPlayer';
import { useRouteMatch } from "react-router-dom";
function DownloadMusic() {
//getting id param
const {
params: { id },
} = useRouteMatch('/downloadMusic/:id');
//converting the string id to number:
const num = Number(id);
//finding the song with song.id= num
const song = songsList.find(item => item.id === num);
return(
<div>
<DownloadCard className='col-md-6'
id= {song.id}
url= {song.url}
image={song.image}
name={song.name}
singer={song.singer}
/><br/>
<AudioPlayer id = {song.id}/>
</div>
);
}
export default DownloadMusic;
Just be careful about type of id, it must be number. this little one took me a long time.
:) Good luck!
You can create a function like this in the song card page
const onSongCardClick = () => {
history.push({
pathname: '/downloadMusic',
state: {id: this.props.id, url: this.props.url}
})
}
...
...
<Button onclick="onSongCardClick()" className="btn btn-outline-dark">go to download page</Button>
And get those values in Download music page by
const { id, url } = this.props.location.state
Also pass url as props to song card from songs page

What is the best way to go about conditional rendering in the case of very similar components?

I render different landing pages based on whether the user is a professor, student, or not logged in. The landing pages are very similar; the only difference is the buttons displayed. I know I can go around this using inline conditions or simple if-else statements. However, I was wondering what the best practices are to implement conditional rendering in this case. I know higher order components (HOCs) can help but I was not sure if they are overkill in this particular case.
To be on the same page, here are the different Landing components that I currently render using if-else statements.
Landing.js (unlogged users):
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import { withEither } from '../../helpers/withEither';
import LandingStudent from './LandingStudent';
import LandingProfessor from './LandingProfessor';
import './Landing.css';
const Landing = ({ history }) => {
return(
<div className="header">
<div className="text-box">
<h1 className="header-primary">
<span className="header-primary-main">
QME
</span>
<span className="header-primary-sub">
the best way to ask questions
</span>
</h1>
</div>
</div>
);
};
export default Landing;
LandingProfessor.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import RaisedButton from 'material-ui/RaisedButton';
import './Landing.css';
const LandingProfessor = ({ history }) => {
return(
<div className="header">
<div className="text-box">
<h1 className="header-primary">
<span className="header-primary-main">
QME
</span>
<span className="header-primary-sub">
the best way to ask questions
</span>
</h1>
<RaisedButton
className="btn-animated btn-landing"
label="Create Class"
onClick={() => history.push('/courses/new')}
/>
<RaisedButton
className="btn-animated btn-landing"
label="Dashboard"
onClick={() => history.push('/courses')}
/>
</div>
</div>
);
};
export default withRouter(LandingProfessor);
LandingStudent.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import RaisedButton from 'material-ui/RaisedButton';
import './Landing.css';
const Landing = ({ history }) => {
return(
<div className="header">
<div className="text-box">
<h1 className="header-primary">
<span className="header-primary-main">
QME
</span>
<span className="header-primary-sub">
the best way to ask questions
</span>
</h1>
<RaisedButton
className="btn-animated btn"
label="Join Class"
onClick={() => history.push('/courses/join')}
/>
</div>
</div>
);
};
export default withRouter(Landing);
A 'trick' could be to append a className on the root div for landscape, namely 'student' 'professor' or 'logout' and use css display: none to hide unwanted items in each scenarios
Another approach would be to keep your 3 components but delegate all the rendering to a common 'LandingRenderer' component that would accept boolean properties like 'showJoinClassButton' 'showCreateButtonButton' etc.
Then your 3 components render would look like domething like this
LandingProfessor: (props) => (
<LandingRenderer
showJoinClassButton={false}
showCreateClassButton={true}
...
{...props} />
)
I would opt for composition over inheritance here, meaning create many reusable components and compose your top-level components with those reusable ones rather than make your top-level components inherit from one common component type. If you follow the latter approach, you might end up with a laundry list of props that I argue is questionably better than what you have right now.
To start, you could componentize your header:
Header.js
export default function Header(props) {
return (
<h1 className="header-primary">
{props.children}
</h1>
);
}
Header.Main = function HeaderMain(props) {
return (
<span className="header-primary-main">
{props.children}
</span>
);
};
Header.Sub = function HeaderSub(props) {
return (
<span className="header-primary-sub">
{props.children}
</span>
);
};
and use it in Landing.js:
import Header from './Header.js';
const Landing = ({ history }) => {
return(
<div className="header">
<div className="text-box">
<Header>
<Header.Main>QME</Header.Main>
<Header.Sub>the best way to ask questions</Header.Sub>
</Header>
</div>
</div>
);
}
I don't think hoc in this particular case is an overkill.
I think whenever you can use hoc for better readability and useability, use it.
Using some of recompose hocs (you should install recompose: npm install recompose)
export const renderByConditions = (...conditions) => compose(
...conditions.map(condition =>
branch(condition[0], (condition[1] && renderComponent(condition[1])) || renderNothing, condition[2] && renderComponent(condition[2]))
)
)
You should pass arrays with the signature:
[condition, left (if the condition is true), right (else)]
condition - (ownProps) => youCondition
left - Component | empty
right - Component | empty
when left is empty - if the condition is true, it will render null.
when right is empty - if the condition is not true, it will render the component we wrapped
Then you can use the hoc:
renderByConditions(
[props => props.landing, props => <Landing {...props}/>],
[props => props.proffesor, LandingProfessor],
[props => props.student, LandingStudent],
[Component => Component, DefaultComponent]
)
I would reccommend start using recompose hocs, they are great!

Dynamically rendered Tag is always lowercase

I am trying to output some svgs and output them from a list, here is my render method:
render() {
const renderTag = () => {
const Tag = this.props.id
return(<Tag />)
}
return (
<div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
{renderTag()}
</a>
</div>
)
}
However, the DOM node is always lowercase i.e. <facebook> rather than <Facebook> this.props.id is correctly rendered to the console as Facebook. Can anyone tell me why react or the browser incorrectly renders as lowercase, and therefore not the component, and how to fix?
It's a technical implementation of React, all tags get lowercased on this line here, AFAIK it's not possible to render non-lowercased tags and that is by design.
Read more here.
i suggest that you would take a look at this article about dynamic components.
The most relevant example from the article:
import React, { Component } from 'react';
import FooComponent from './foo-component';
import BarComponent from './bar-component';
class MyComponent extends Component {
components = {
foo: FooComponent,
bar: BarComponent
};
render() {
const TagName = this.components[this.props.tag || 'foo'];
return <TagName />
}
}
export default MyComponent;
you most likely have a limited amount of components that could be rendered, so you might create a dictionary that contain a key (name of the component) to the component itself (as shown in the example) and just use it that way:
import Facebook from './FaceBook';
import Twitter from './Twitter';
const components = {
facebook: Facebook,
twitter: Twitter
};
render() {
return <div key={this.props.name} className="social-box">
<a className={this.props.id + "-link"}>
<components[this.props.id] />
</a>
</div>;
}
I find the answer eventually. #TomMendelson almost had the answer, but it needed fleshing out a bit more.
A function to create the component outside of the render method, suggested by #ShubhamKhatri actually did the job. Here's the final code:
import React from 'react';
import Facebook from './svg/Facebook';
import LinkedIn from './svg/LinkedIn';
import Twitter from './svg/Twitter';
import Pinterest from './svg/Pinterest';
class SocialMediaBox extends React.Component {
renderElement(item) {
const Components = {
'Facebook': Facebook,
'Twitter': Twitter,
'Pinterest': Pinterest,
'LinkedIn': LinkedIn
}
return React.createElement(Components[item], item);
}
render() {
const Element = this.renderElement(this.props.id)
return
(
<div>
{Element}
</div>
)
}
}
export default SocialMediaBox;
Thanks for the question and answers; alongside the answers given in Dynamic tag name in jsx and React they helped me to find a solution in my context (making a functional component in Gatsby with gatsby-plugin-react-svg installed):
import React from "react"
import FirstIcon from "../svgs/first-icon.inline.svg"
import SecondIcon from "../svgs/second-icon.inline.svg"
import ThirdIcon from "../svgs/third-icon.inline.svg"
const MyComponent = () => {
const sections = [
{ heading: "First Section", icon: () => <FirstIcon /> },
{ heading: "Second Section", icon: () => <SecondIcon /> },
{ heading: "Third Section", icon: () => <ThirdIcon /> },
]
return (
<>
{sections.map((item, index) => {
const Icon = item.icon
return (
<section key={index}>
<Icon />
<h2>{item.heading}</h2>
</section>
)
})}
</>
)
}
export default MyComponent
As mine is a Gatsby project I used the above mentioned plugin, but it itself process svgs with svg-react-loader so the basic principle should work in any React project using this package.

Categories

Resources