React onClick pass object ID from a JSON array - arrays

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.

Related

Add dynamic link from an object in React

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'>

Im not understanding why I'm recieving the error "Cannot read property 'map' of undefined" in React

Im trying to load the information from a database to my React App. Everything is working as expected except for the map function I'm running. Im trying to list all the genres of the given movie in the div.
import React, { Component } from 'react';
import axios from 'axios';
import ReactPlayer from 'react-player';
import play from '../../img/play-icon.svg';
import './hero-display.scss';
class HeroDisplay extends Component {
constructor() {
super();
this.state = {
movie: {},
trailer: false
}
}
componentDidMount() {
axios.get('https://petflix.herokuapp.com/movie')
.then((response) => {
this.setState({
movie: response.data
})
})
.catch((error) => {
console.error(error);
});
}
openTrailer = () => {
this.setState({
trailer: true
});
}
closeTrailer = () => {
this.setState({
trailer: false
});
}
render() {
const { movie, trailer } = this.state;
return (
<>
<div className="hero-display__container" style={{backgroundImage: `url(${movie.backdropURL})`}}>
<div className="hero-display__movie-information">
<img className="hero-display__movie-logo" src={movie.logoURL} alt=""/>
<h1 className="hero-display__heading">Watch Now</h1>
<p className="hero-display__movie-description">{movie.description}</p>
<div className="hero-display__control-buttons">
<button onClick={this.openTrailer} className="hero-display__button play">
<img src={play} alt=""/>
Play Trailer
</button>
</div>
</div>
<div className="hero-display__rating">
<p>{movie.rating}</p>
</div>
</div>
<div className={trailer ? "movie__trailer showing" : "movie__trailer hidden"}>
<div className="movie-trailer__container">
<button className="movie-trailer__close-button" onClick={this.closeTrailer}></button>
<ReactPlayer className="video-player" light={movie.backdropURL} volume={0} loop={true} playing={true} image={movie.backdropURL} muted={false} url={movie.trailerURL}/>
<div className="movie__information">
<div className="movie-trailer__title-add">
<h1 className="movie-trailer__title">{movie.title}</h1>
<button className="movie-trailer__button"></button>
</div>
<p className="movie-trailer__rating">{movie.rating}</p>
<p className="movie-trailer__director">Directed By: {movie.director}</p>
<p className="movie-trailer__description">{movie.description}</p>
{movie.genres.map((genre) => {
return (
<p className="movie-trailer__genres">{genre}</p>
)
})}
</div>
</div>
</div>
</>
)
}
}
export default HeroDisplay;
The map function works in a different functional component when the state is passed as a prop, but not in the class component itself. Id appreciate the help.
An example of a movie object that is stored in state.
{
"title": "101 Dalmatians",
"description": "A brave young man is thrust into adulthood as he and his courageous team of sled dogs embark on a grueling and treacherous cross-country marathon.",
"director": "Stephen Herek",
"genres": [
"Adventure",
"Family",
"Comedy"
],
"rating": "G",
"backdropURL": "https://www.themoviedb.org/t/p/original/mz75dVXfen4J1Z0R8DbTenXNywz.jpg",
"posterURL": "https://www.themoviedb.org/t/p/original/8o2ADoAyG796UwTjwBFjPyBz0yG.jpg",
"trailerURL": "https://www.themoviedb.org/video/play?key=K2CTJS12RdI",
"logoURL": "https://www.themoviedb.org/t/p/original/noruOCRUoWndPPDPJYdR7kVsiq5.png"
}
It is happening due to the generes property of movie state is not available before the api call.
So instead of direct access of property.
{movie.genres.map((genre) => {
return (
<p className="movie-trailer__genres">{genre}</p>
)
})}
use optional chaining with ?.
{movie?.genres?.map((genre) => {
return (
<p className="movie-trailer__genres">{genre}</p>
)
})}
This part:
{movie.genres.map((genre) => {
return (
<p className="movie-trailer__genres">{genre}</p>
)
})}
Should be
{movie.genres && movie.genres.map((genre) => {
return (
<p className="movie-trailer__genres">{genre}</p>
)
})}
Alternatively, you could do movie?.genres?.map((genre) => {...
Otherwise movie.genres is undefined on first render.

Loop through array in React and create elements (not list items) from it

I'm learning React and have done a fair bit of research on this. I've quickly discovered that the map() function is what I think I should be using for looping through an array.
But, my problem is all the examples in the React documentation and in the SO questions I've viewed use <ul> and <li> HTML elements to handle the output.
I'm not sure that my use case is "correct" as far as React structure is concerned, but, I want to output a <div> with some child elements each time I loop through.
Here is my static code so far:
const Comment = () => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={chachi}/>
<p className="commenter-name">Scott Baio</p>
</div>
<div className="commenter-comment">
<p>Ehhhh!! Joanie loves Chachi!!!</p>
</div>
</div>
</div>
)
}
This works, but now if I have additional comments I want to be able to serve up the same block of code again but with the new commenters name, image, comment content etc.
So I've now made an array to house my multiple commenters, and things aren't really working anymore.
import React, { Component } from 'react'
import fonzie from "./img/the-fonz.jpg";
import chachi from "./img/chachi.jpg";
const Comments = [
{
id: 1,
name: 'Hello World',
photo: fonzie,
comment: 'Welcome to learning React!'
},
{
id: 2,
name: 'Hello World',
photo: chachi,
comment: 'Welcome to learning React!'
}
];
const commentEngine = props.Comments.map((comment) =>
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
);
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
<commentEngine />
</div>
);
}
}
export default Comments
At this point I'm unsure how to verify if my loop is working in the first place and how to get the output properly displaying in my app.
Any help is greatly appreciated, as is insight into whether or not this is well structured or should be separate components.
Thanks!
It sounds like you want to re-use the Comment component with data passed by Comments. In React, this is done via props.
So, you'll want to pass the images's src, the name, and the description:
const comments = [
{
id: 1,
name: "Hello World",
photo: fonzie,
comment: "Welcome to learning React!",
},
{
id: 2,
name: "Hello World",
photo: chachi,
comment: "Welcome to learning React!",
},
];
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{comments.map((comment) => {
return (
<Comment
key={comment.id} // https://reactjs.org/docs/lists-and-keys.html
name={comment.name}
imgSrc={comment.photo}
comment={comment.comment}
/>
);
})}
</div>
);
}
}
Notice that I've renamed the constant array Comments to comments so that the name doesn't clash with the Comments component.
Then in the Comment component, you can access these props via the argument passed to the function component:
const Comment = (props) => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={props.imgSrc} />
<p className="commenter-name">{props.name}</p>
</div>
<div className="commenter-comment">
<p>{props.comment}</p>
</div>
</div>
</div>
);
};
Additionally, we can make the code a bit less verbose by leveraging object destructuring:
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{comments.map(({ id, name, photo, comment }) => {
return (
<Comment key={id} name={name} imgSrc={photo} comment={comment} />
);
})}
</div>
);
}
}
// ...
const Comment = ({ imgSrc, name, comment }) => {
return (
<div className="commenter">
<div className="commenter-inner">
<div className="commenter-left">
<img src={imgSrc} />
<p className="commenter-name">{name}</p>
</div>
<div className="commenter-comment">
<p>{comment}</p>
</div>
</div>
</div>
);
};
const commentEngine = (comments) => {
return comments.map((comment)=>{
return (
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
)})
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{commentEngine(props.Comment)}
</div>
);
}
}
Now when you render Comments you need to pass the Comment props.
<Comments Comment={Comments}/>
USAGECASE
import React, { Component } from 'react'
import fonzie from "./img/the-fonz.jpg";
import chachi from "./img/chachi.jpg";
const Comments = [
{
id: 1,
name: 'Hello World',
photo: fonzie,
comment: 'Welcome to learning React!'
},
{
id: 2,
name: 'Hello World',
photo: chachi,
comment: 'Welcome to learning React!'
}
];
const Comment = props =>
const {comment} = props;
<div className="commenter" key={comment.id}>
<div className="commenter-inner">
<div className="commenter-left">
<img src={comment.photo}/>
<p className="commenter-name">{comment.name}</p>
</div>
<div className="commenter-comment">
<p>{comment.comment}</p>
</div>
</div>
</div>
);
class Comments extends Component {
render() {
return (
<div className="comments-section col-md-10 offset-md-1 col-sm-12">
<h4>Comments</h4>
{Comments.map((comment,index) => <Comment key={'[CUSTOM_KEY]'} props={comment}> )}
</div>
);
}
}
export default Comments
ANSWER
First of all, You can use index parameter in Array.map
Secondly, if you want to use list component you can make Single Component like <Comment comment={comment}> and you can use it with Array.map
And It is very good to study How to make functional component

Pass an object value in props to another component

Consider the code below, I need to pass an id which is in object format to another component by props. But I have try many time and it not working. I think I may have some thing mistake, but I'm not sure where is it.
Main page (updated):
render(props){
const data = this.state.data;
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal}/>
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
openModal (Updated):
openModal = (id) => {
this.setState(
{
displayModal: true,
id: id
});
console.log(id) // i can get the id from here id=1
};
PurchaseInfoView to get the id (Updated).
class PurchaseInfoView extends Component {
render() {
console.log(this.props.id) // get undefined
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //cannot get it
</div>
);
}
}
export default PurchaseInfoView;
console.log result:
If you want to pass an object to props here are the steps:
define the object in your parents state.
pass the object in props when calling components
get the object from props in child.
Here you are missing the second step!
You should try these:
MainPage
render(props){
const { data, modalObject, displayModal } = this.state; //use destructuring is more readable
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={displayModal} closeModal={this.closeModal} modalObject={modalObject}/> //pass the object from destructuring state as props
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
OpenModal
openModal = (id) => {
this.setState(
{
displayModal: true,
modalObject: {id: id, ...any others key/val pair}
});
};
PurchaseInfoView
class PurchaseInfoView extends Component {
render() {
const { modalObject} = this.props; //here get your object from props
console.log(modalObject.id);// here you have the object
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {modalObject.id}</p>
</div>
);
}
}
Tell me if you have any question in comment ;)
NB: i did this with an object (aka {} ) if you needed more things in your modal than just id. If just id is needed you just have to replace the modalObject by just the "id" you need
Cheers!
EDIT: for this solution to work you have to either:
initialise your state to this at least:
this.state={ modalObject : { id: ''}}
or make a not null test in your child component before displaying the element like so:
Id: {modalObject && modalObject.id ? modalObject.id : ' '}
These are needed because on first render your state will have the initial state you setted so if you didnt set anythin or didnt test for a value... well... it's undefined! :)
(note if id is null instead of having an undefined error you will have a blank space displaying in your modal)
Guess you are calling it wrongly. It should be {this.props.id}
render() {
console.log(this.props.id);
return (
<div className="Modal">
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //Changed line
</div>
);
}
Inside main page pass the id to PurchaseInfoView and access it as a prop
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal} id={this.state.id}/>

How do I add in an additional property for each individual mapped item after already making a call to a 3rd party API?

I want to add a feature that increments how many "likes" someone gets similar to FB. The profiles are getting passed in through an Axios GET request through a 3rd party API. When a user clicks on the like button, the amount of likes someone gets should increment by 1. The code I previously wrote in handleClicks() increments everyone's likes by 1 rather than just one individual person. The data is passed into cards[] in one chunk.
App.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
class App extends React.Component {
constructor(props) {
super(props)
this.state = {cards: [], numVotes: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("This is working!");
this.setState(numVotes: state.numVotes + 1})
}
componentDidMount() {
axios.get('/')
.then(res => {
this.setState({cards: res.data})
console.log(this.state);
})
}
render() {
return (
<div className="main-container">
<Header />
<CardList
cards={this.state.cards}
handleClick={this.handleClick}
/>
<hr className="ui divider"></hr>
</div>
);
}
export default App;
const CardList = props => {
const cards = props.cards.map(card => {
return <Card image={card.image_url}
name={card.name}
title={card.title}
blurb={card.bio}
handleClick={props.handleClick}
numVotes={props.numVotes}
/>
})
return <div className="ui divided items">
{cards}
</div>
}
Card.js
const Card = (props) => {
return (
<div className="card-component item">
<div class="ui small rounded image">
<img className="portrait"
src = {props.image}
onError={(e)=>{e.target.onerror = null; e.target.src='https://image.shutterstock.com/image-vector/no-image-available-vector-illustration-260nw-744886198.jpg'}}
/>
</div>
<div class="content">
<a className="header">{props.name}</a>
<div class="meta">
<span className="title">{props.title}</span>
</div>
<p className="blurb">{props.blurb}</p>
<p><span className="question"> Want to work with {props.name}?</span>
<span className="like-button" onClick={props.handleClick}>
<img className="icon" src={icon} />Yes!
</span>
</p>
<p className="yes-amt">{props.numVotes} people have said Yes!</p>
</div>
</div>
)
}
You need to first decide how to identify each card as unique so you can update the correct one. If you have an id that would be ideal, but I'm going to assume the name is unique since its a value in your question.
// pass the unique identifier to handler
handleClick(name) {
this.setState((prevState) => ({
// map over the previous cards and return a new array
cards: prevState.cards.map((card) => {
// If the name matches the current card, change it
if (card.name === name) {
return {...card, numVotes: card.numVotes + 1};
} else {
// Otherwise just return the same card unchanged.
return card;
}
})
}))
}
Then in your component use it like this:
// Use inline function so we can pass it a prop as a parameter
<span className="like-button" onClick={() => props.handleClick(props.name)}>

Resources