Add dynamic link from an object in React - reactjs

I'm new to React and I'm trying to insert a Link in a React Component. I made an object, and each item contains an external link. This the object :
export const myList =
[
{
"id":"P1",
"title":"Title1",
"description":"The first description",
"link":"https://random-link.io",
"cover":require("../img/myImg1.webp")
},
{
"id":"P2",
"title":"Title2",
"description":"The second description",
"link":"https://random-link2.io",
"cover":require("../img/my2ndImg.webp")
},
...
];
The main idea is to create pages for each item of the list, and a link to an external page to see more information.
I tried to do this :
function Page() {
const id = useParams();
const pageList = myList.find(list => list.id === id.id);
return(
<>
{
pageList ? (
<div className="Page">
<img className="ListCover" src={pageList?.cover} alt={pageList?.title}/>
<div className="information-list">
<span className="title-list">{pageList?.title}</span>
</div>
<div className="description-list">
<Dropdown titre="Description" description={pageList?.description} link={pageList?.link} />
</div>
</div>
) : <Navigate replace to="/404"/>
}
</>
)
}
export default Page;
In the Dropdown component, I made this :
function Dropdown({title, description, link}) {
const [open, setOpen] = useState(false);
return(
<div className="dropdown" id={`dropdown-${title}`}>
<div className="header-dropdown">
<div className="title-dropdown">{title}</div>
<span className={`arrow-dropdown ${open}`} onClick={() => setOpen(!open)}>
<img src={arrow} alt="Open it"/>
</span>
</div>
{
/* if dropdown is TRUE then it show the description */
open && <div className="description-dropdown">{description}
See more </div>
}
</div>
);
}
export default Dropdown;
The problem is that the link sends me to : http://localhost:3000/[object%20Object]; with another method I got http://localhost:3000/myProjet/https://random-link.io

I believe that the only issue in your code is that you are trying to use an object as the parameter for href which takes a string, try to just put link in there and it should work. it should look like:
<a href={link} rel='noreferrer'>

Related

How can I create Single Page

How can I pass map items (title, category and images) in my id.jsx file.
Basically, I just want to create a single page for my projects. But I can only access post ID. I don't know how to pass other data items.
'Projects folder'
[id].js
import { useRouter } from "next/router";
const Details = () => {
const router = useRouter();
return <div>Post #{router.query.id}
// Single Project Title = {project.title} (like this)
</div>;
};
export default Details;
index.js
import { MyProjects } from "./MyProjects";
const Projects = () => {
const [projects, setProjects] = useState(MyProjects);
{projects.map((project) => (
<Link
href={"/projects/" + project.id}
key={project.id}
passHref={true}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
If I understand your question correctly, you want to send some "state" along with the route transition. This can be accomplished using an href object with the "state" on the query property, and the as prop to hide the query string.
Example:
{projects.map((project) => (
<Link
key={project.id}
href={{
pathname: "/projects/" + project.id,
query: {
id: project.id,
category: project.category,
title: project.title
}
}}
passHref={true}
as={"/projects/" + project.id}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
...
const Details = () => {
const router = useRouter();
return (
<div>
<div>Post #{router.query.id}</div>
<div>Title {router.query.title}</div>
<div>Category {router.query.category}</div>
</div>
);
};

How to Create Show/Hide button when Looping in React

I am working on a development site and am having an issue. The issue is that I am looping over the data file in order to create some project cards. Each project card has a show more/show less button to display/hide card descriptions.
My problem is that the current setup is mapping over the data and causing it so that whenever one gets clicked, all three either open or close simultaneously. Please help me to fix this issue. Relevant code is shown below:
Data example:
{
name: "Hot in the Biscuit",
id: "3a34",
image: "/images/bonnie.jpg",
description: "A multi-page front-end business website for a local restaurant in Koh Samui, Thailand. Custom design built with vanilla JavaScript, HTML and CSS.",
link: "https://www.xxxxxxxxxxxxx.com",
date: "2021",
github: "https://github.com/xxxxxxxxxxxxxxxxxxxxxx"
},
Hero file where Showcase Component is rendered:
<h1>Featured Projects</h1>
<div>
<Showcase/>
</div>
Showcase where cards are created (UNNECCESSARY CODE REMOVED - classes and image):
const Showcase = () => {
const {readMore, setReadMore} = useContext(HeroContext)
const {toggleMenu} = useContext(NavbarContext)
return(
<>
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image/>
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{ readMore[id] ? <-- THIS IS WHERE YOU NEED AN ID
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore[id] ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub/>
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
})}
</>
)
}
export default Showcase
So I just need some help on figuring out how to set it up so that each button knows which card is being clicked and only that button open. Thank you very much for helping me. I appreciate your time and help immensely.
Bodi
It will be easier if you split showcase item to a new component.
const ShowCaseItem = ({ data }) => {
const { toggleMenu } = useContext(NavbarContext)
const [readMore, setReadMore] = useState(false)
const { id, name, image, link, github, description, date } = data;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image />
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{readMore ?
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub />
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
}
const Showcase = () => {
const { readMore, setReadMore } = useContext(HeroContext)
return (
<>
{showcase.map((item) => <ShowCaseItem data={item} />)}
</>
)
}
export default Showcase
You should update the HeroContext state to hold a reference to the ids that are shown/hidden.
Example:
const [readMore, setReadMore] = useState({});
const readMoreToggler = (id) => setReadMore(state => ({
...state,
[id]: !state[id], // <-- toggle boolean value
}));
// context value
{
readmore,
setReadMore: readMoreToggler, // pass readMoreToggler as setReadMore
}
...
const { readMore, setReadMore } = useContext(HeroContext);
...
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
...
</div>
<div>
...
<div>
<h4>
{readMore[id] // <-- check by id if toggled true|false
? description
: `${description.substring(0, 100)}...`
}
<button
onClick={() => setReadMore(id)} // <-- pass id to toggle
>
{readMore[id] ? "Show Less" : "Show More"} // <-- check by id if toggled true|false
</button>
</h4>
</div>
<div>
...
</div>
)
})}

How to open dynamic modal with react js

I am trying to convert the HTML/Javascript modal to React js.
In Reactjs, I just want to open the modal whenever the user clicks the View Project button.
I have created a parent component (Portfolio Screen) and a child component (Portfolio Modal). The data I have given to the child component is working fine but the modal opens the first time only and then does not open. Another problem is that the data does not load even when the modal is opened the first time.
Codesandbox link is here.
https://codesandbox.io/s/reverent-leftpad-lh7dl?file=/src/App.js&resolutionWidth=683&resolutionHeight=675
I have also shared the React code below.
For HTML/JavaScript code, here is the question I have asked before.
How to populate data in a modal Popup using react js. Maybe with hooks
Parent Component
import React, { useState } from 'react';
import '../assets/css/portfolio.scss';
import PortfolioModal from '../components/PortfolioModal';
import portfolioItems from '../data/portfolio';
const PortfolioScreen = () => {
const [portfolio, setportfolio] = useState({ data: null, show: false });
const Item = (portfolioItem) => {
setportfolio({
data: portfolioItem,
show: true,
});
};
return (
<>
<section className='portfolio-section sec-padding'>
<div className='container'>
<div className='row'>
<div className='section-title'>
<h2>Recent Work</h2>
</div>
</div>
<div className='row'>
{portfolioItems.map((portfolioItem) => (
<div className='portfolio-item' key={portfolioItem._id}>
<div className='portfolio-item-thumbnail'>
<img src={portfolioItem.image} alt='portfolio item thumb' />
<h3 className='portfolio-item-title'>
{portfolioItem.title}
</h3>
<button
onClick={() => Item(portfolioItem)}
type='button'
className='btn view-project-btn'>
View Project
</button>
</div>
</div>
))}
<PortfolioModal portfolioData={portfolio} show={portfolio.show} />
</div>
</div>
</section>
</>
);
};
export default PortfolioScreen;
Child Component
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const PortfolioModal = ({ portfolioData, show }) => {
const portfolioItem = portfolioData;
const [openModal, setopenModal] = useState({ showState: false });
useEffect(() => {
setopenModal({
showState: show,
});
}, [show]);
return (
<>
<div
className={`portfolio-popup ${
openModal.showState === true ? 'open' : ''
}`}>
<div className='pp-inner'>
<div className='pp-content'>
<div className='pp-header'>
<button
className='btn pp-close'
onClick={() =>
setopenModal({
showState: false,
})
}>
<i className='fas fa-times pp-close'></i>
</button>
<div className='pp-thumbnail'>
<img src={portfolioItem.image} alt={`${portfolioItem.title}`} />
</div>
<h3 className='portfolio-item-title'>{portfolioItem.title}</h3>
</div>
<div className='pp-body'>
<div className='portfolio-item-details'>
<div className='description'>
<p>{portfolioItem.description}</p>
</div>
<div className='general-info'>
<ul>
<li>
Created - <span>{portfolioItem.creatDate}</span>
</li>
<li>
Technology Used -
<span>{portfolioItem.technologyUsed}</span>
</li>
<li>
Role - <span>{portfolioItem.Role}</span>
</li>
<li>
View Live -
<span>
<NavLink to='#' target='_blank'>
{portfolioItem.domain}
</NavLink>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default PortfolioModal;
You don't have to use one useState hook to hold all your states. You can and I think you should break them up. In the PortfolioScreen component
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
I changed the function Item that is used to set the active portfolio item to toggleItem and changed it's implementation
const toggleItem = (portfolioItem) => {
setData(portfolioItem);
setVisible(portfolioItem !== null);
};
You should use conditional rendering on the PortfolioModal, so you won't need to pass a show prop to it, and you'll pass a closeModal prop to close the PortfolioModal when clicked
{visible === true && data !== null && (
<PortfolioModal
data={data}
closeModal={() => toggleItem()} // Pass nothing here so the default value will be null and the modal reset
/>
)}
Then in the PortfolioModal component, you expect two props, data and a closeModal function
const PortfolioModal = ({ data, closeModal }) => {
And the close button can be like
<button className="btn pp-close" onClick={closeModal}>
...

Toggle specific div (id) within a react component

I have a site built with post-components to show articles in a feed. Inside the component, I have a button that opens a modal onClick. I use useState to toggle on the modal which works perfectly fine. The problem is that since the toggle is put on the modal-div inside the component.. every single post modal opens whenever I click one of the buttons. I want to open only the targeted post modal (with the sam post id as the button I’m clicking). I can’t figure out how to do this…in react.
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
After trying suggested solution using object instead on bolean. I now receive this error message
[Error message][1]for the following code:
const [toggles, setToggles] = useState ({});
let id;
const createToggler = (id) = () => {
setToggles(prev => { [id] : !prev[id] })
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
const data = useStaticQuery(graphql`
query {
allMarkdownRemark (
sort: { order: DESC, fields: [frontmatter___date] }
){
edges {
node {
frontmatter {
id
title
name
details
featuredImage {
childImageSharp {
fluid(maxWidth: 800) {
...GatsbyImageSharpFluid
}
}
}
}
html
fields {
slug
}
}
}
}
}
`)
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = edge.node.frontmatter.id;
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post" id={edge.node.frontmatter.id}>
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} id={edge.node.frontmatter.id}>
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
[1]: https://i.stack.imgur.com/VhIYF.png
like this.
use a object instead of a single boolean.
const [toggles, setToggles] = useState ({});
const createToggler = (id) = () => {
setToggle(prev => { [id]: !prev[id] }) // atmost one id is true. others is undefine or false.
// setToggle(prev => { ...prev, [id]: !prev[id] }) // or support multi modal at same time. but I think you don't want it.
}
...
return (
<section className="posts">
{data.allMarkdownRemark.edges.map((edge) => {
const id = ... // get your id form edge.
const toggle = toggles[id];
const toggler = createToggler(id);
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{edge.node.frontmatter.title}</h2>
<h2 className="name">{edge.node.frontmatter.name}</h2>
<button className="readMoreBtn" onClick={toggler}>{toggle ? <h2 className="readMore">Read more</h2> : <h2 className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={edge.node.frontmatter.featuredImage.childImageSharp.fluid} />
<div className={toggle ? 'hide' : 'postCopy'} >
<Close close={toggler} />
<h3>{edge.node.frontmatter.details}</h3>
<div className="copy" dangerouslySetInnerHTML= {{__html: edge.node.html}}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)}
)}
</section>
)
}
export default Posts;
I solved my problem like this
import React, {useState} from "react"
import Img from "gatsby-image"
import './posts.css';
import cancel from '../images/cancel.png'
const Post = ({title, name, id, image, details, html}) => {
const [toggle, setToggle] = useState (true);
const toggler = () => {
setToggle(prev => !prev)
}
const selectPost= (event) =>{
let id = event.target.id,
postCopy = document.getElementById('hide' + id);
toggler.call(postCopy);
}
return (
<div className="post">
<div className="postDescrip">
<h2 className="postTitle">{title}</h2>
<h2 className="name">{name}</h2>
<button className="readMoreBtn" onClick={selectPost}>{toggle ? <h2 id={id} className="readMore">Read more</h2> : <h2 id={id} className="readMore">Read less</h2>}
</button>
</div>
<Img className="postImg" fluid={image} />
<div id={'hide' + id} className={toggle ? 'hide' : 'postCopy'} >
<button aria-label="Close" onClick={selectPost} className="closeBtn">
<img alt="close-button" src={cancel}/>
</button>
<h3>{details}</h3>
<div className="copy" dangerouslySetInnerHTML= {html}></div>
<h4>Read the full article in Issue One</h4>
</div>
</div>
)
}
export default Post;

React onClick pass object ID from a JSON array

I'm very new to React and coding in general. I have a project where I need to be able to click on a movie poster and have it open a new page where it would show details on the movie.
Below is the JSON
[
{
"id": 1,
"name": "The Matrix",
"image": "/assets/images/posters/the_matrix.jpg"
},
{
"id": 2,
"name": "Jaws",
"image": "/assets/images/posters/jaws.jpg"
},
{
"id": 3,
"name": "Jurassic Park",
"image": "/assets/images/posters/jurassic_park.jpg"
}
]
Below is the component that I'm passing the individual movies through
class LibraryCard extends Component {
render () {
return (
<div>
<div className="container">
<div className="row">
{films.map((props) => {
// Returns film poster and title
return <div onClick={() => props.handleUpdateFilm(props.id)}>
<a onClick={() => {window.location.href="filmdetails/" + props.name;}}><img src={props.image} alt={props.name} key={props.id}></img></a>
<h6>{props.name}</h6>
</div>
})}
</div>
</div>
</div>
);
}
}
The card is being passed into the Films page which displays all the posters for the film
const Films = () => (
<div>
<LibraryCard />
</div>
);
export default Films;
Below is the Details page that will display all the info for each film that's clicked.
class Details extends Component {
render() {
return (
<div><h1 align="center">This is a story...</h1>
<div className="container">
<div className="row">
{Films.map(Film => <img src={Film.image} key={Film.id} id={Film.id} alt={Film.name} handleClick={this.handleClick} />)}
</div>
</div>
</div>
);
}
}
Pardon the inefficiency of the code. Again, I'm new to this. I just need help getting it to load to a new page for each movie. Any help would be appreciated.
What you are looking for here is a curried function :
handleUpdateFilm = id => ev => {
//Do whatever you want with the id variable here
}
This function will accepts 2 sets of parameters, the first time it is called, only the id will be set, the second time, the event will be given and the instructions in it will be executed.
Here is how you can use and bind it in your code :
class LibraryCard extends Component {
handleUpdateFilm = id => ev => {
//Do whatever you want with the id variable here
}
render() {
return (
<div>
<div className="container">
<div className="row">
{films.map(({ id, name, image }) => // Props deconstruction
<div onClick={this.handleUpdateFilm(id)}> //Your function binding
<a onClick={() => { window.location.href = "filmdetails/" + name; }}>
<img src={image} alt={name} key={id}></img>
</a>
<h6>{name}</h6>
</div>
)}
</div>
</div>
</div>
);
}
}
I also removed the variable named props in your code, if something is not a prop, try to avoid naming it props.

Resources