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

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>

Related

Next/React: How to store fetched data from backend (Sanity) in localstorage?

I have a gallery page that shows 6 categories (as images that are used as links) that, when one is selected, will move onto the next page and show a list of referenced sets. The next page should show the category name that was selected.
I'm currently trying to store the category name (which is located in my backend) into local storage once it is clicked, which I can then extract into the next page. This is what I currently have:
import React, { useState, useEffect } from 'react';
import { client, urlFor } from '../lib/client';
import { Header, Footer } from '../components';
import Link from 'next/link';
const gallery = () => {
// fetches sanity data
const [categoryData, setCategories] = useState(null);
useEffect(() => {
client.fetch(
`*[_type=="category"]{
categoryImage_alt,
category_image{
asset->{
_id,
url
}
},
category_name,
contained_sets
}`)
.then((data) => setCategories(data))
.catch(err => console.error(err));
}, [] );
const selectedCategory = [];
const saveCategory = (e) => {
selectedCategory.push({})
localStorage.setItem('selectedCategoryName', JSON.stringify(selectedCategory));
console.log(localStorage)
}
return (
<div>
<Header />
<main className="main-gallery">
<div className="title">
<div className="title-line-left"></div>
<h2>categories</h2>
<div className="title-line-right"></div>
</div>
<div className="categories">
<ul className="categories-container">
{categoryData && categoryData.map((category, index) => (
<li key={index}>
<Link href="/sets"><img src={urlFor(category.category_image).auto('format').url()} alt={category.categoryImage_alt} onClick={saveCategory(category.category_name)} /></Link>
</li>
))}
</ul>
</div>
</main>
<Footer />
</div>
)
}
export default gallery
(I've also tried putting the onClick event in the Link tag instead of the img tag, same result). At the moment however, it doesn't store anything when the category is clicked. Instead, when I click onto the navbar menu that opens up this page with categories, it immediately prints out this 6 times (and nothing happens when I click onto one of the categories):
replace your saveCategory function with
const saveCategoryName = (categoryName) => {
localStorage.setItem('selectedCategoryName', categoryName);
}
and replace the onclick function with
onClick={() => saveCategoryName(category.category_name)}
now selected category name will stay in localStorage

Nextjs: Props object undefined

Goal: to map over 'portfolio' projects stored in a local json file, and pass each object to a card component i created.
The portfolio component is in the pages folder, and the console.log(line: 15 - correctly displays array of json objects // [ {}, {}, {} ]...
import React from "react"
import Card from "../components/Card"
export async function getStaticProps() {
const res = await import("./db.json")
return {
props: {
projects: res.projects,
},
}
}
const portfolio = ({ projects }) => {
//this console.log works as expected
console.log(projects)
return (
<div className='portfolio'>
{projects.map((project, id) => (
<Card key={id} props={project} />
))}
</div>
)
}
export default portfolio
the issue began when attempting to destructure the props object passed to the card component. The first thing i noticed were no images, but upon closer inspection: no title, no working links, etc. i receive undefined... and I'm not completely sure why.
import React, { useState } from "react"
import Image from "next/image"
const Card = ({ image, title, github, demo }) => {
console.log(title)
const [hovered, setHover] = useState(false)
return (
<div
className='portfolio-card'
onMouseEnter={() => setHover(!hovered)}
onMouseLeave={() => setHover(!hovered)}
>
<div className='portfolio-card-info'>
<h3 className='portfolio-card-title'>{title}</h3>
{hovered && <p>ReactJs</p>}
{hovered && <p>Recoil</p>}
<ul className='portfolio-links'>
<li>
<a href={github}>Source</a>
</li>
<li>
<a href={demo}>Demo</a>
</li>
</ul>
</div>
<Image className='portfolio-image' src={image} alt='portfolio-image' />
</div>
)
}
export default Card
the console.log from portfolio: 15: 10 shows the array of objects as expected.
the console.log from card: 5:10 is undefined.
Array(3) [ {…}, {…}, {…} ] portfolio:15:10
undefined Card.js:5:10
I checked the react dev tools, if i select the first card for example, i can see the props.can anyone tell me what i'm missing here, i've been on this now for a day and a half.

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

React - Show only the clicked user

In the following app, I'm accessing the random user API and show a list of 12 users.
App.js
import React, { useState } from 'react'
import UserList from './components/UserList'
const App = props => {
const [id, setID] = useState(null)
console.log(`Passed variable to App.js is: ` + id)
return (
<>
<UserList setID={setID} />
</>
)
}
export default App
UserList.js
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const UserList = ({ setID }) => {
const [resources, setResources] = useState([])
const fetchResource = async () => {
const response = await axios.get(
'https://api.randomuser.me/?results=12'
)
setResources(response.data.results)
}
useEffect(() => {
fetchResource()
}, [])
return (
<ul>
{resources.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
<button
onClick={() => setID(item.login.uuid)}
>
Details
</button>
</div>
</li>
))}
</ul>
)
}
export default UserList
The above code is working. But now I want that if I click on the button for any of those listed users, only that user get showed.
How can I do that?
The response JSON looks like this:
Easiest way would be to apply a filter on your ressources variable to only display the user with selected uuid.
To do that, first you need to share selected id with UserList component:
App.js
<UserList id={id} setID={setID} />
Then update UserList accordingly:
UserList.js
const UserList = ({ id, setID }) => {
return (
<ul>
{ resources
.filter(user => Boolean(id) ? user.login.uuid == id : true )
.map(item => (
<li key={item.name.first}>
<div>
<h2>{item.name.first} {item.name.last}</h2>
{ Boolean(id) ?
<button onClick={() => setID(null)}>
Hide
</button>
:
<button onClick={() => setID(item.login.uuid)}>
Details
</button>
}
</div>
</li>
)
}
</ul>
)
}
That way, you will only display the select user in you <ul>. To unselect your user, just call setID(null)
Show user profile instead of list
If that solution work to filter your list, I guess you might want to adapt your page to show all details from your user. Next step would be to implement multi pages using react-router-dom with a url container your user uuid.
You can look at the url-params example which might be exactly what you are looking for.
Here's a slightly detailed option that extends beyond a single component but more easy to scale on account of modularity.
Create a new react component in a new file say, UserDetails.js
Now you need a way to navigate to this new page when the button is clicked.
So in your App.js you need a router like
import { BrowserRouter, Switch} from 'react-router-dom'
Then in your App.js file wrap all your components in the router:
class App extends Component {
render() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/user-list" component={UserList} />
<Route exact path="/detail" component={UserDetails}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
export default App;
Now you are ready to navigate to the user details page, when the button is clicked. So add a function like goToDetails like:
<button onClick={() => goToDetails(item)}>
Next define the function that navigates to the next page
goToDetails(item) {
this.props.history.push('/detail', {selectedUser:item:});
}
The history prop is available above because we earlier wrapped the entire app in BrowserRouter.
In the details page, you get the selectedUser details as a prop:
const selectedUser = this.props.location.state.selectedUser;
Now you can render it however you want.

React - Importing the JSON object only once

The following simple React component is importing a JSON file (data.js) as an object and list the items inside it.
List.js
import React from 'react'
import jsonResponse from './data'
function ZooList ({ setID }) {
const setURL = (e) => {
window.history.pushState(null, null, '/' + e)
setID(e)
}
const list = jsonResponse.animals.map((item) => {
return (
<li key={item.id}>
<div>
<img
src={item.image_url}
alt={item.name}
onClick={() => setID(item.id)}
/>
<h3>{item.name}</h3>
<p>
<b>Distribution</b>: {item.distribution}
</p>
<button onClick={() => setURL(item.id)}>More...</button>
</div>
</li>
)
})
return (
<ul>
{list}
</ul>
)
}
export default List
Now in the above page, if you click on button "More...", it calls another React component called Tile.js as fallow:
Tile.js
import React from 'react'
import jsonResponse from './data'
function Tile ({ setID, newID }) {
const clearURL = (e) => {
window.history.pushState(null, null, '/')
setID(null)
}
return (
<div>
<div>
<img
src={jsonResponse.animals[newID].image_url}
alt={jsonResponse.animals[newID].name}
/>
<h2>{jsonResponse.animals[newID].name}</h2>
<p><b>Distribution</b>: {jsonResponse.animals[newID].distribution}</p>
<StyledParagraph>{jsonResponse.animals[newID].details.long}</StyledParagraph>
<button onClick={() => clearURL(null)}>Back to overview</button>
</div>
</div>
)
}
export default Tile
The problem is that the second component is also importing the JSON file (data.js).
How can I avoid importing the data.js twice?
Generally, what would be a better way to write this app?
Imports are cached, so if you return directly a JSON with import jsonResponse from './data', the first component will import it, while the second will get it from import cache;
You can try for example, to export an instance of a class, change one of its property and then check that property in another component that make use of that import.
A ready-to-pick and very common usage example of that cache is the configureStore of react-boilerplate: it exports the store instance so whatever component import it will refer to the same store.

Resources