How to render component with for loops in react - reactjs

I'm trying to create a carousel in react. I have two states, the first and last index of the carousel itself. The states is used to determine how many products are displayed in the carousel. Both are initialized as the number 0 and 4, respectively. The code for displaying the products are as follows:
const displayCarouselItems = () => {
for (let i = firstCarouselIndex; i < lastCarouselIndex; i++) {
return <ProductCard product={products[i]} />;
}
};
and here is how I called the function in my JSX:
return (
<div className="container flex justify-between relative">
<button onClick={() => nextButtonHandler()}></button>
{displayCarouselItems()}
<button onClick={() => prevButtonHandler()}></button>
</div>
);
It only renders one product card in my page. Would anyone know why the for loop doesn't iterate?

It only shows one component because once you return from the displayCarouselItems function, it's over, no more looping
You might want to return an array of elements, like this:
const displayCarouselItems = () => {
const renderValue = []
for (let i = firstCarouselIndex; i < lastCarouselIndex; i++) {
renderValue.push(<ProductCard product={products[i]} />)
}
return renderValue
};
Then it would be better with a key
// Use index as key
renderValue.push(<ProductCard product={products[i]} key={i} />)
// Or even better, an id, which avoid issues with rendering
// if the sequence of the items change
renderValue.push(<ProductCard product={products[i]} key={products[i].id} />)
That said, I think a more declarative pattern would be to use map over your products collection, for example in a pure function like the following:
const renderCarouselItems = products =>
products.map(p => (
<ProductCard key={p.id} product={p} />
));
Which, can as well be used directly in the markup unless you need it somewhere else:
return (
<div className="container flex justify-between relative">
<button onClick={() => nextButtonHandler()}></button>
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
<button onClick={() => prevButtonHandler()}></button>
</div>

Related

How to get specific data from api with condition

Hello so i tried to make an website using Goole Books API. I want to get the listPrice from the object, but theres some of the book that doesnt have the listPrice in them. So for the example in object number 1 there is some code called saleability: "NOT_FOR_SALE" meanwhile object number 2 have and saleability: "FOR_SALE". If i tried to map the data, there is a error Uncaught TypeError: i.saleInfo.saleability.listPrice is undefined. How do you make spesific condition for this problem.
This is the code :
const CardBooks = (props) => {
const url = "https://www.googleapis.com/books/v1/volumes?q=:keyes&key=AIzaSyDIwDev4gFHRqCh4SSaO9eLKEeI7oYt6aE&maxResults=27"
const result = "&maxResults=40"
const [bookHome, setBookHome] = useState([]);
const [modalShow, setModalShow] = React.useState(false);
useEffect( () => {
axios
.get(`${url}`)
.then( (res) => {
setBookHome(res?.data?.items)
console.log(res?.data?.items)
})
.catch(console.error);
}, [])
return (
<div>
<Container fluid className='wrapper'>
{bookHome && bookHome.map((i, index) => {
return(
<div className='image-container' key={index}>
<div className="book read">
<div className="cover">
<img src={i.volumeInfo.imageLinks.thumbnail} />
</div>
<div className="info">
<h3 className="title">{i.volumeInfo.title}</h3>
</div>
<Example
thumbnail={i.volumeInfo.imageLinks.thumbnail}
title={i.volumeInfo.title}
description={i.volumeInfo.description}
category={i.volumeInfo.categories}
page={i.volumeInfo.pageCount}
language={i.volumeInfo.language}
publisher={i.volumeInfo.publisher}
published={i.volumeInfo.publishedDate}
link={i.volumeInfo.previewLink}
epub={i.accessInfo.epub.isAvailable}
currency={i.saleInfo.saleability.listPrice.currencyCode}
price={i.saleInfo.saleability.listPrice.amount}
/>
</div>
</div>
)
})}
</Container>
</div>
)
}
export default CardBooks
Basically you just need a null/undefined check, a quick and dirty solution:
currency={i.saleInfo.saleability.listPrice ? i.saleInfo.saleability.listPrice.currencyCode : ''}
It's better to use conditional rendering and/or passing the whole object to the component and handling it inside.

react.js error handling inside functional component

I have a component which receives a list of items through props.
It looks like this:
const component = (props) => {
return (
<ul>
{props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};
edit:
and the child looks like this:
const ListItem = (props) => {
return (
<li key={props.key}>
<h4>{props.title}</h4>
<div>
<img src={props.imgSrc} alt='thumbnail'
/>
</div>
</li>
);
};
The list comes from an API and there are cases in which the values I am assigning will be undefined or not available (imgSrc for example). This breaks the entire rendering of the app.
How can I handle errors in a way that will skip the problematic item and continue with the mapping? It usually means this is a deleted item so I wish to skip it all together.
I usually wrap the code with a try-catch or if statement but I am not allowed to do it here.
There are many options to solve that. For example, you could use the filter method before your .map call.
const component = (props) => {
return (
<ul>
{props.list.filter((item) => item.img.url !== undefined).map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};
Another possible option could be Error Boundaries. I don't think that they are what you need, but it could be interesting for you anyways.
You can conditional rendering.
Array.isArray(props.list) &&
props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
));
You can only map over the array if it is an array as:
const component = (props) => {
return (
<ul>
{Array.isArray(props.list) && props.list.map((item) => (
<ListItem key={item.Id} title={item.title} imgSrc={item.img.url} />
))}
</ul>
);
};

Each child in a list should have a unique "key" prop images

I am familiar with this warning and I know what it means but I do not get it here. I have a simple image previewer, user click on right / left arrow buttons to display new images. I get this warning at this component's render function. What I believe it might be because of the image that I set as background. I have an array of images and depending on which one should be displayed on the screen, I set it as background image for entire component.
Even if I could do something like const images = { 0: "image1", 1: "image2" } I do not see how this solves the problem. Or maybe the problem is somewhere else.
const images = [
"image1",
"image2",
"image3"
];
let currentImageIndex = 0;
export default function Section() {
const [selectedImage, setSelectedImage] = useState(
() => images[currentImageIndex]
);
const handleMoveNextImage = () => {
if (currentImageIndex === images.length - 1) {
currentImageIndex = 0;
} else {
currentImageIndex++;
}
setSelectedImage(() => images[currentImageIndex]);
};
const handleMovePrevImage = () => {
if (currentImageIndex === 0) {
currentImageIndex = images.length - 1;
} else {
currentImageIndex--;
}
setSelectedImage(() => images[currentImageIndex]);
};
return (
<div
className="item-section"
style={{
backgroundImage: `url(${selectedImage})`,
backgroundSize: "cover",
}}
>
<div className="icons-over-img-main">
<div className="arrow-icons-btns">
<div
className="placeholder-for-img-switch-arrows left"
onClick={handleMovePrevImage}
>
</div>
<div
className="placeholder-for-img-switch-arrows"
onClick={handleMoveNextImage}
>
</div>
</div>
<div className="img-position-points">
{images.map((image, index) => (
<div className="point">
<div
key={index}
className={`${image === selectedImage && "current-point"}`}
></div>
</div>
))}
</div>
</div>
</div>
);
}
The topmost component returned inside a .map callback is the one that needs the key. This:
{images.map((image, index) => (
<div className="point">
<div
key={index}
className={`${image === selectedImage && "current-point"}`}
></div>
should be
{images.map((image, index) => (
<div className="point" key={index}>
<div
className={`${image === selectedImage && "current-point"}`}
></div>
Since the images array looks to be static, using the key for the index is safe. Declaring it as an array is just fine too. const images = ["image1","image2","image3"];
Also, in order to not interpolate false as the class name (that's not intentional, right?), use the conditional operator instead:
className={image === selectedImage ? "current-point" : ''}

How to pass Mobx store as props to react compoent

I have this app that uses mobx, in it there is a component called "Listings" that uses some state from mobx to render a list of items.
The way it is right now, is that the Listings component gets the data it needs(store.restaurantResults[store.selectedFood]) from inside of it by using the mobx store like so:
const Listings = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="pa2">
{store.restaurantResults[store.selectedFood] &&
store.restaurantResults[store.selectedFood].map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<p>{rest.name}</p>
</div>
);
})}
</div>
));
};
But i think this is wrong, as it couples the component with the data, I want instead to pass that data via props so it can be reusable.
What is the correct way to do this? Right now my App looks like this, where it's being wrapped around a storeProvider:
function App() {
return (
<StoreProvider>
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way"/>
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings />
</div>
</StoreProvider>
);
}
My idea is to extract everrything inside the StoreProvider into another component that has a store and returns the jsx via useObserver so that I can acces the store and then pass what i need as props to the other components. like this:
const Wapper = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way" />
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings listings={store.restaurantResults[store.selectedFood]} />
</div>
))
}
And then on the listings component change the hard coded store.restaurantResults[store.selectedFood] inside to use the props that is being passes now, that is called listigs like so:
const Listings = ({listings}) => {
const store = React.useContext(StoreContext);
return useObserver(() => (
store.loading
? <Loading />
: <div className="pa2">
<div className="flex flex-wrap">
{listings &&
listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
));
};
And this works, but is this the right way to go about this?
As <Listings/> can be provided with listing and loading you can:
const Listings = ({listings, loading}) => {
if(loading) return <Loading />
return (
<div className="pa2">
<div className="flex flex-wrap">
{listings && listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
);
}
No observables used, no useObservable required.
You want to useObservables on store for listings then no reason to wrap all components with useObservable. You should wrap <Listings/> only.
I usually define my store as a global, so every component has visibility of it:
class Store {
#observable myVar
}
global.store = new Store()
And in my components i just use it:
#observer
export default class MyComponent extends React.Component {
constructor () {
super()
store.myVar = 0
}
setMyVar (a) {
store.myVar += 1
}
render () {
return <button onClick={this.setMyVar}>
Clicked {store.myVar} times
</button>
}
}

Reactjs have <div> wrapper the elements on every 4th elements

React group 4 items in a div with class row. At the moment you can see below I have a <div> group around the articles.
// articles is array of article object
<div className="row">
<section className="section--6-or-4-items section-featured campaign-teasers">
{articles.map(item => <Teaser {...item} key={item.id} />)}
</section>
</div>
Should I create a new component which have <div> wrapper around and only accept 4 arrays at a time to render out the articles.
Your best bet here is to build your array of articles into an array of 4-length arrays. You can do this using lodash's handy chunk method.
Once you've chunked your articles, you can iterate over them in your JSX, something like this:
const sections = chunk(articles, 4);
return (
<div className="row">
{sections.map((articles, i) => (
<section className="..." key={i}>
{articles.map(item => <Teaser {...item} key={item.id} />)}
</section>
)}
</div>
);
Edit: If you don't want to use lodash, you can use reduce to great effect:
function chunk(array, size) {
return array.reduce((chunks, item, i) => {
if (i % size === 0) {
chunks.push([item]);
} else {
chunks[chunks.length - 1].push(item);
}
return chunks;
}, []);
}
Personally, I extract rendering to dedicated methods.
<div className="row">
<section className="section--6-or-4-items section-featured campaign-teasers">
{ this.renderArticles() }
</section>
</div>
Renderer method here...
renderArticles() {
return createChunks(articles, 4).map((chunk, idx) => (
<div key={idx}>
{ chunk.map((props, idx) => <Teaser key={idx} {...props} />) }
</div>
));
},
And finally, function which chunks array into smaller array on N size
const createChunks = (array, size) => {
const copy = array.concat();
const chunks = [];
while (copy.length) {
chunks.push( copy.splice(0, size) );
}
return chunks;
}

Resources