Use props from component in another file - javascript

As you might understand, I'm pretty new to React. I tried to search StackOverflow and google this, but I cannot find anything that answers this question.
I'm creating this travel review page. I save a form to a Firebase database and I display it using a component. However, I want to display, say, only the name of a country, but I cannot manage to do that. I'm pretty sure that I must pass it as a prop but I cannot figure out how to do that, exactly.
What I want is to, use this component and decide in the component what to view.
If I'd use this component in some other .js-file, I'd like to do something like this:
and display only the data that lies withing the field countryName.I'm going to use the country names as a list, that's why I want to single out just the names.
I've played around using props but I cannot get my head around it. Perhaps someone could help out?
Thanks.
This is the function which I use as a component:
const CountryList = () => {
const countries = useCountries()
return (
<div className="countries">
{countries.map((country) =>
<div key={country.id}>
<div className="time-entry">
Name of review: {country.revName} <br/>
Name of Country: {country.countryName}<br/>
Destination 1: {country.dest1}<br/>
Destination 2: {country.dest2}<br/>
Destination 3: {country.dest3}<br/>
Beer price: {country.beerPrice}<br/>
Food price: {country.foodPrice}<br/>
Hostel price: {country.hostelPrice}<br/>
Review: {country.review}<br />
<img src={country.url} alt="no-img" />
</div>
</div>
)}
</div>
)
}

Super simple way could be to pass an array of "fields" you want to display as a prop, and conditionally render UI. If the displayFields array includes the name of the country property then render that "field".
Example:
const CountryList = ({ displayFields = [] }) => {
const countries = useCountries();
return (
<div className="countries">
{countries.map(country => (
<div key={country.id}>
<div className="time-entry">
{displayFields.includes("revName") && (
<div>Name of review: {country.revName}</div>
)}
{displayFields.includes("countryName") && (
<div>Name of Country: {country.countryName}</div>
)}
{displayFields.includes("dest1") && (
<div>Destination 1: {country.dest1}</div>
)}
{displayFields.includes("dest2") && (
<div>Destination 1: {country.dest2}</div>
)}
{displayFields.includes("dest3") && (
<div>Destination 1: {country.dest3}</div>
)}
{displayFields.includes("beerPrice") && (
<div>Beer price: {country.beerPrice}</div>
)}
{displayFields.includes("foodPrice") && (
<div>Food price: {country.foodPrice}</div>
)}
{displayFields.includes("hostelPrice") && (
<div>Hostel price: {country.hostelPrice}</div>
)}
{displayFields.includes("review") && <div>Review: {country.review}</div>}
{displayFields.includes("imgUrl") && <img src={country.url} alt="no-img" />}
</div>
</div>
))}
</div>
);
};
Usage:
<CountryList displayFields={["countryName", "beerPrice"]} />

Related

With React, what is the best way to pass a useState array back and forth between components?

I had some help with a previous issue with my little project, but I have a new problem I can't seem to understand. My program takes an array of objects (call them cards), and displays an on-screen card for each element in the array. I have an edit button for each card, which should open the edit form for the chosen item, and pre-populate it with its current state - this all works.
I want to be able to edit the item, save it back in place into the array, and have that 'card' updated. This is the main component:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
// dummy data hardcoded for now
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]);
const [showForm, setShowForm] = React.useState(false);
const [currentid, setCurrentid] = React.useState(0);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => {
setShowForm(!showForm);
setCurrentid(id);
}}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
</div>
</div>
);
})}
{showForm && (
<div>
<Editform cards={cards} setCards={setCards} id={currentid} />
</div>
)}
</div>
);
}
export default Displaycards;
and this is the Editform.js which it calls at the bottom. As far as I can tell I'm passing my array, setter function, and id of the card I want to edit:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
function saveChanges(cardtochange) {
setCards(
cards.map(
(
card // intention is map back over the original array, and if the id matches that
) =>
card.id === id // of the edited card, write the changed card back in at its ID
? {
id: id,
gamename: cardtochange.gamename,
maxplayers: cardtochange.maxplayers,
freespaces: cardtochange.freespaces,
tablenum: cardtochange.tablenum,
}
: card // ... or just write the original back in place.
)
);
}
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button
type="button"
className="playbutton"
onClick={saveChanges(thisCard)} //remove to see edit form - leave in for perpetual loop.
>
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
If I comment out the onClick for the button, the page renders. If it's in there, the page gets stuck in an infinite loop that even React doesn't catch.
The way I'm trying to recreate my array is based on advice I've read here, when searching, which said to take the original array, rebuild it item-for-item unless the ID matched the one I want to change, and to then write the new version in.
I suspect there might be a way to do it with my setter function (setCards), and I know there's an onChange available in React, but I don't know a) how to get it to work, or b) how - or even if I need to - pass the changed array back to the calling component.
Your function is invoked directly upon components render:
{saveChanges(thisCard)}
Rename it to a callback style signature:
{() => saveChanges(thisCard)}
Also do add a jsfiddle/ runnable snippet for answerers to test ✌️.
Edit:
About the array of objects passing and updates, at your part the code is good where filter is used. We can apply idea of moving update logic to parent where data is located.
Now id + updated attributes could be passed to the update callback in child.
To give you hint, can use spread operator syntax to update items out of existing objects.

How to .Map over different props that are passed into a component?

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>
);
}

Objects are not valid as a React child (found: object with keys {Dname, recipe_diet}). If you meant to render a collection of children, use an array

I can get all my api info, but when create a new "recipe" and I try to show it on the Front, I get that error, but just on the "recipe" that I created, :c
import React from "react";
import './estilos/Cartita.css'
export default function Card({image, title, diets, healthScore, createdInBd, id}){
return (
<div className="containerr">
<div className="card">
<h2>{id}</h2>
<img src={image} alt="img not found" width="200px" height="250px"/>
<h2>NOMBRE:</h2>
<h5>{title}</h5>
<p>Tipo de dieta</p>
<div >
{
(!diets.length)?
<span>This Recipe doesnt have a diet
</span> :
diets.map((diet) => (
<span><li key={diets}> {diet}</li></span>
)
)}
</div>
<h2>HEALTSCORE:</h2>
<h5>{healthScore}</h5>
<h5>{createdInBd}</h5>
</div>
</div>
);
}
and this is the Api info, the top one is from the api and the other one is the one that I created:
enter image description here
due to your screenshot diets can be array of strings or array of objects, therefore you need to check if diet is an object
{!diets.length ?
<span>This Recipe doesnt have a diet</span> :
diets.map((diet) => (
<span><li key={diet.id}>{typeof diet === 'string' ? diet : diet.Dname}</li></span>
)
)}

Transform array of objects having HTML within its properties into JSX-elements

so like in my question, I have an issue with creating an element.
{collection.map((element,index ) => {
return <div class='food-carousel' key={index} >{element}</div>;
})}
the collection is an array that holds two images and titles. So each literal should give
<div class='food-carousel'> <img ..> <img ..> <h1>some</h1><div>
but for now, I got
react-dom.development.js:55 Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {html}). If you meant to render a collection of children, use an array instead.
also, I would like to add a condition if index=2 div class='food-carousel active-element'>
Updated:
.map(({html}, index ) => {
return <div key={index} >{html}</div>;
})}
That mapping fix an error but instead of elements I got pure HTML
I assume, you pass an object having HTML source within its properties as {element}. So, just like the error reads, you cannot do that and should go something, like:
{
collection.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
>
{Object.values(element)}
</div>
))
}
Above will work if object properties have JSX as values, if it's escaped string, you would need to use dangerouslySetInnerHTML as #JosephD suggested.
Following is the live-demo of both approaches:
const { render } = ReactDOM
const collection1 = [
{html: <span>That is my first item</span>},
{html: <span>That is my second one</span>},
{html: <span>And here goes the third</span>},
],
collection2 = [
{html: '<span>That is my first item</span>'},
{html: '<span>That is my second one</span>'},
{html: '<span>And here goes the third</span>'},
]
const Test1 = () => (
<div>
{
collection1.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
>
{Object.values(element)}
</div>
))
}
</div>
)
const Test2 = () => (
<div>
{
collection2.map((element,index ) => (
<div
className={`food-carousel${index==2 ? ' active-element' : ''}`}
key={index}
dangerouslySetInnerHTML={{__html:element.html}}
/>
))
}
</div>
)
render (
(
<div>
<Test1 />
<hr />
<Test2 />
</div>
),
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
However, it is important to note, that throwing in HTML-markup is not a good practice at all and you should consider building up JSX elements from element properties data instead.

Conditional in reactJS for a specific bootstrap class depending on number of items rendered?

I'm new to ReactJS. ProductInfo is a card in my project that users can fill out with information. I want to set in my ReactJS code below a conditional in the Product const:
Where if only 2 ProductInfo cards are filled out, apply a col-md-6 to the className, so that the 2 cards are centered on the page.Other wise, use col-md-4 if there are more than 2 ProductInfo cards filled out. How would I go about doing this?
const ProductInfo = ({ title, blurb }) => (
<div>
{title && <h3 className="color-accent">{title}</h3>}
{blurb &&
<span dangerouslySetInnerHTML={{__html: productBlurb}} />
}
</div>
);
const Product = (props) => (
//If only two ProductInfo cards are present, apply a boostrap class of col-md-6, otherwise apply the bootstrap class below
<div className="hs-product col-xs-12 col-sm-12 col-md-4" >
{props.productIconLink ?
<a href={props.link} target="_blank">
<ProductInfo {...props} />
</a>
:
<ProductInfo {...props} />
}
</div>
);
I'd recommend you passing the ProductInfo data to Product through props. You could use a structure like this:
const ProductInfoData = [
{
title: 'title1',
blurb: 'blurb1'
},
{
title: 'title2',
blurb: 'blurb2'
},
...
]
Using this approach, you can then map through the array and render each ProductInfo. You'll even know how many ProductInfo components you are going to render, using props.productInfoData.length. You could do something like this:
const Product = (props) => {
const className = 'hs-product col-xs-12 col-sm-12 ' + this.props.productInfoData.length == 2 ? 'col-md-6' : 'col-md-4';
return (
<div className={className}>
{this.props.productInfoData.map((data) => {
if (this.props.productIconLink) {
return (
<a href={props.link} target="_blank">
<ProductInfo {...data} />
</a>
)
} else return <ProductInfo {...data} />;
})}
</div>
);
};
This is how you would render <Product />:
const ProductInfoData = [
{
title: 'title1',
blurb: 'blurb1'
},
...
]
<Product productInfoData={ProductInfoData} />
Also, if I were you, I would avoid using dangerouslySetInnerHTML. You usually don't have to use this and can rewrite your components so that they don't need this.

Categories

Resources