I'm new to React but hopefully someone can help!
So I've just created a component that takes in a value (via prop) and then .maps over that value creating an Image slider. The props are all an array of objects that contain different values such as :
const Songs = [
{
artist: 'Artist Name',
song: 'Song Name',
lenght: '2:36',
poster: 'images/....jpg'
},
{
artist: 'Artist Name',
song: 'Song Name',
lenght: '2:36',
poster: 'images/....jpg'
},
]
I have been making the same component over and over again because I don't know how to make the 'prop'.map value dynamic. Essentially I don't know how to change the value before the .map each different prop.
Here's an example. I want to make 'Songs'.map dynamic so the new props can replace that so they can also be mapped. Maybe there's another way. Hopefully some can help.
import React from 'react';
import { FaCaretDown } from 'react-icons/fa';
function ImageSlider({Songs, KidsMovies, Movies, TvShows}) {
return (
<>
{Songs.map((image, index) => (
<div className="movie-card">
<img src={'https://image.tmdb.org/t/p/w500' + image.poster_path}
className='movie-img' />
<h5 className='movie-card-desc'>{image.original_title}</h5>
<p className='movie-card-overview'>{movie.overview}</p>
</div>
))}
</>
);
}
export default ImageSlider;
Given your example,
I feel like all you need is render ImageSlides for each array
function ImageSlider({ items }) {
return (
<>
{items.map((item, idx) => (
<div ... key={idx}> // be careful to not forget to put a key when you map components
...
</div>
))}
</>
);
}
When rendering your component
function OtherComponent({ songs, kidsMovies, movies, tvShows }) {
return (
<div>
<ImageSlider items={songs} />
<ImageSlider items={kidsMovies} />
<ImageSlider items={movies} />
<ImageSlider items={tvShows} />
</div>
);
}
Related
function TinderCards() {
const [people, setPeople] = useState([
{
name: "Model Baby",
url:"https://www.themodelskit.co.uk/wp-
content/uploads/2021/10/shutterstock_1431963185.jpg",
age:22
},
{
name: "Seema Jaswal",
url:"https://static.standard.co.uk/2021/06/14/16/euro_2020_live_seema_jaswal_01-1.jpg?
width=968&auto=webp&quality=50&crop=968%3A645%2Csmart",
age:32
},
{
name: 'Baby',
url: '../assets/IMG_20210811_105110_849.webp'
age: 34
}
]);
I am pullin data from a datebase that is like the data i created above for illustration purposes
useEffect(() => {
const allPeople = query(collection(db, "people"))
onSnapshot(allPeople, (snapshot) => (
setPeople(snapshot.docs.map((doc) => doc.data()))
))
return () => {
second
}
}, [])
I am updating the app with the information from the database
return (
<div>
<div className='tinderCards__cardContainer'>
{people.map((person) => (
<TinderCard className="swipe" key={person.name} preventSwipe={["up, down"]} onClick=
{() => {setPer(person.name)}} >
<div className='card' style={{ backgroundImage: `url(${person.url})`}}>
<h3>{person.name}</h3>
</div>
</TinderCard>
))}
</div>
</div>
)
}
export default TinderCards
but how do i update my sidebar with this infomatiom and have the name and other infomation change, eachtime the name changes in the main app
function RightSidebar() {
return (
<div className='rightSidebar'>
<div className='rightSidebar__contents'>
<h1>About</h1>
<Card className='rightSidebar__card'>
<div className='card__nameContents'>
<CardHeader className='card__nameAge' title = {name} subheader = {age}
avatar = {<VerifiedIcon className='activeIcon verified' />} />
<CardHeader className='card__active' title = "active" avatar=
{<FiberManualRecordIcon className='activeIcon' />} />
</div>
The app is quite small do i really need redux or is there a way to track the changes with useState hook?
How far is the common parent of RightSidebar and TinderCards?
If one level up or not too far, store the people data as state of the common parent, pass the data to right sidebar and a function to update the sidebar to TinderCards.
If common parent is far, you can use the React Context API (or the useContext hook).
You don't need redux for this
I have App, that is the parent component and I have the Child component:
The Child component gets a props called items so it can be reused depending on the data. It the example there is data, data1 and data2.
The thing is that I want to set a cookie from the parent component, to set the cookie I need the property link from data2, but I am already mapping data2 in the Child component.
What can I do to obtain the value of the property link in the parent component to pass it as an arguement here:
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
This is the whole example code:
import * as React from 'react';
import './style.css';
const data = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data1 = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data2 = [
{ title: 'hey', link: 'link/hey' },
{ title: 'hey1', link: 'link/he1' },
{ title: 'hey2', link: 'link/he2' },
];
export default function App() {
const [, setCookie] = useCookie('example');
const handleUpdate = (cookie) => {
setCookie(null);
setCookie(cookie);
};
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
</div>
);
}
export function Child({ items }) {
return (
<div>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}
Thank you!
If you want to get the link from the Child component you can simply add a link parameter in the callback:
<Child
onClick={(link) => handleUpdate(link)}
items={data2}
/>
Then from the Child you just need to call the onClick prop:
export function Child({ items, onClick }) { // here make sure to add the prop while destructuring
<a href={item.link} onClick={() => onClick(item.link)}>Go to title</a>
The map method doesn't change the array that it is called on, it just returns a new array, do the items array doesn't get affected at all here, so you can just call it normally like so:
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate(data2[0].link)
}
items={data2}
/>
</div>
);
Also, your Child component needs to accept the onClick function as a prop like so:
export function Child({ items, handleClick }) {
return (
<div onClick={handleClick}>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}
I have some code in codesandbox that is made up of 4 divs, 2 with the category "Book" and 2 with the category "Article". Some buttons at the top should trigger if all the divs should be displayed, only the books, or only the articles. All the buttons show every div currently, so the page doesn't change and it looks like the state stays the same
Here is the code which is on the sandbox
App.js
import React, { useState } from "react";
/* import Container from './design/Container' */
import Test from "./Test";
const posts = [
{
title: "React Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Using React Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
},
{
title: "Angular Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Angular Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
}
];
export default function App() {
const [productItems, setProductItems] = useState(posts);
function handleButton(e) {
console.log(e.target.value);
if (e.target.value === "All") {
setProductItems(posts);
} else {
setProductItems(
posts.filter((p, i) => <div key={i}>p.category === e.target.value</div>)
);
}
setProductItems(posts);
console.log(productItems);
}
return (
<div>
<Test posts={productItems} handleButton={handleButton} />
</div>
);
}
Test.js
import React from "react";
function Post({ p,title, content, category }) {
return (
<React.Fragment>
<div>
<h3>{p.title}</h3>
<div>{p.content}</div>
<br />
<i>
in <b>{p.category}</b>
</i>
</div>
</React.Fragment>
);
}
export default function Test({handleButton, posts = [] }) {
return (
<React.Fragment>
<div>
<button value="All" onClick={handleButton}>
All
</button>
<button value="Book" onClick={handleButton}>
Book
</button>
<button value="Article" onClick={handleButton}>
Article
</button>
</div>
<div>
{posts.map((p) => {
return <Post key={p.title} p={p} />;
})}
</div>
</React.Fragment>
);
}
style.scss
.App {
font-family: sans-serif;
text-align: center;
}
You had a few things wrong, one was that you handleButton required an argument but you weren't passing one to it. You need to call it like onClick={(e) => handleButton(e)} another was that you set the state of product items again after your if statement. You had already set it to the filtered value, but then you overwrote it with the unfiltered value like setProductItems(posts); so you have to remove this line. Another was that your filter function didn't really make sense. I would look it up and learn more about it. It takes a function that returns a boolean; it doesn't return a div.
SOLUTION
(sandbox)
App.js
import React, { useState } from "react";
import Test from "./Test";
const posts = [
{
title: "React Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Using React Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
},
{
title: "Angular Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Angular Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
}
];
export default function App() {
const [productItems, setProductItems] = useState(posts);
function handleButton(e) {
console.log(e.target.value);
if (e.target.value === "All") {
setProductItems(posts);
} else {
setProductItems(posts.filter((p) => p.category === e.target.value));
}
console.log(productItems);
}
return (
<div>
<Test posts={productItems} handleButton={handleButton} />
</div>
);
}
Test.js
import React from "react";
const Post = ({ pa }) => {
return (
<React.Fragment>
<div>
<h3>{pa.title}</h3>
<div>{pa.content}</div>
<i>
in <b>{pa.category}</b>
</i>
</div>
</React.Fragment>
);
};
export default ({ posts = [], handleButton }) => (
<>
<div>
<button value="All" onClick={(e) => handleButton(e)}>
All
</button>
<button value="Book" onClick={(e) => handleButton(e)}>
Book
</button>
<button value="Article" onClick={(e) => handleButton(e)}>
Article
</button>
</div>
<div>
{posts.map((pa, i) => (
<Post key={i} pa={pa} />
))}
</div>
</>
);
My JSX won't show up properly on my React webpage instead I get this output:
<div class='card'>NaNSasha<img src= NaN />Boddy Cane</div>.
The component:
import React, {Component} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component{
state = {
string : '',
}
componentDidMount(){
let posts = [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
];
for(let i =0; i < posts.length; i ++){
const header = `<div class='card'>${+posts[i].title}`;
const body = posts[i].author;
const image = `<img src= ${+posts[i].image} />`;
const description = `${posts[i].location}</div>`;
const concatThis = header + body + image + description
this.setState({
string: concatThis
});
};
};
render(){
return(
<div className='container'>
{this.state.string}
</div>
)
}
}
export default App;
P.S I'm a student
This is what you're looking for. The expression +{} is evaluated as NaN. But please use list rendering.
const image = `<img src= ${+posts[i].image} />`;
^ here
It seems that you are trying to build a string which you then store in a state and render that string after it has been updated. This is unfortunately not how you should use React.
The state should only be raw data, like the posts array with objects. It holds the content and data of the component and should not concern itself on other tasks than that. You obviously can put any type of data in a state, like a string.
state = {
title: 'My food blog',
description: 'Awesome stuff about food',
posts: [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
]
}
The componentDidMount method is triggered whenever the component has been placed on the page and is now working. In there you can do things like making a change to the data or downloading data from the server. It would make sense that you would do that there because then you would first show your component, maybe show it that it is loading and then fetch data from the server. After that is done, update the state of the component with the new data and the render method will be called with the new data. For example (for illustration purposes):
componentDidMount() {
fetch('urlofdatathatyouwant') // Uses AJAX to get data from anywhere you want with the Fetch API.
.then(response => response.json()) // Tells it to read turn the response from JSON into an usable JavaScript values.
.then(data => {
this.setState({
posts: data // Use the new data to replace the posts. This will trigger a new render.
});
});
}
The render method should primarely concern itself with the presentation of the data in your state. In this case it should loop over the elements in the posts state and create a React element for each post.
render() {
const { posts } = this.state;
return(
<div className='container'>
{posts.map(({ title, author, image, location, description }) => (
// Loop over each post and return a card element with the data inserted.
<div className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
))}
</div>
)
}
All put together it would look like this the example below. So the state only holds the data, componentDidMount is a place to do manipulation of your data after the component is on the page and render only outputs the HTML that you need to create with JSX.
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component{
state = {
posts: [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
]
}
componentDidMount() {
// Do something here with the posts if you need to.
}
render() {
const { posts } = this.state;
return(
<div className='container'>
{posts.map(({ title, author, image, location, description }, index) => (
<div key={index} className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
))}
</div>
)
}
}
export default App;
You could even make it a bit nicer by making the card element a component as well. And since it does not have any functionality (yet) it only has to control the output.
const Card = ({ title, author, image, location }) => (
<div className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
)
And then import the card into your App component and use it in the render method.
// App.jsx render.
render() {
const { posts } = this.state;
return(
<div className='container'>
{ /* ...post taking all the properties of each post and passes them to the card element */ }
{posts.map((post, index) => <Card key={index} {...post} />)}
</div>
)
}
This question already has answers here:
Applying className/onClick/onChange not working on Custom React Component
(2 answers)
Closed 5 years ago.
I'm learning React, I have 3 components:
App, contains UserList
UserList, contains a list of cards
UserCard, the content of the previous component
Here's my UserList code:
state = {
users: [
{ id: 1, name: 'Chris', age: 20 },
{ id: 2, name: 'Max', age: 1 },
{ id: 3, name: 'Jean', age: 23 },
{ id: 4, name: 'Luc', age: 30 }
]
}
userList = this.state.users.map(user => {
return (
<div key={user.id} className="column">
<UserCard name={user.name} age={user.age} onClick={this.userClickHandler} />
</div>);
});
userClickHandler = () => {
console.log('clicked !');
};
render() {
return (
<div className="columns">
{this.userList}
</div>);
}
For some odd reason, my userClickHandler does not get triggered when a UserCard is clicked.
Note that I have tried the following:
Changing onClick={this.userClickHandler} to onClick= () => {this.userClickHandler} and that it works when I move the code of my userList in the render method without assigning it to a variable like so:
<div className="columns">
{ this.state.users.map(user => {
return (
<div key={user.id} className="column" onClick={this.userClickHandler}>
<UserCard name={user.name} age={user.age} />
</div>);
}) }
</div>);
What's the apparent problem?
You need to bind the function to the scope here..Sorry about my previous answer. I didn't analyse it properly
<div key={user.id} className="column" onClick={this.userClickHandler.bind(this)}>
Normally, I'd store something like this in a variable in the render section like this. Let me know if this helps. :
render() {
let userList = this.state.users.map(user => {
return (
<div key={user.id} className="column">
<UserCard name={user.name} age={user.age} onClick={this.userClickHandler} />
</div>);
});
return (
<div className="columns">
{userList}
</div>);
}
Also, in the constructor of userList, bind the click handler:
constructor(){
this.userClickHandler = this.userClickHandler.bind(this);
}
This could likely be that you've discovered function hoisting.
Arrow functions are not hoisted.
You are only able to make it invoke it after you've declared it.
A normal function using a function keyword can be invoked at a line number before it is declared.
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting