i use a firebase database and i take data from this base into JSON format. With this data i am using map function and i want to render my data into other components. My code is as shown below. The first component
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
return (
<Row>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</Row>
)
}
The second component that i want to render my data
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{
props.url ? (
<div>
<Card.Img variant="top" src={ props.url.img || 'holder.js/100px160'} />
<Card.Body>
<Card.Title> {props.url.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam according to Nikolas
</Card.Text>
</Card.Body>
</div>
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)
}
</Card>
</Col>
</CardDeck>
</Row>
)
}
With the second component i want to produce the number of Bootstrap-React Cards depending of the number of data i have. For example if i have 6 elements into my JSON file i want in the second component to produce 6 react-bootstrap Cards and print for each some informations like the name.
With the above code i accomplished to pass the props but the props that i console.log is not my data. This is what i get in my console when i
console.log(props)
Can anyone tell how i can pass my data correctly or suggest a better way to do that.
I hope my question is understood. I can give more information i anyone wants
I think this is what you are trying to achieve:
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json");
setUrl(myurl.data);
}
asyncCall();
}, []);
return (
<Row>
{/*{ {url.map((url => ( */}
{/* the url in the arrow function was shadowing the url array that you were trying to pass to the bags componenet */}
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
{/* )
))} */}
</Row>
);
}
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className="my-3 p-3 rounded">
{props.url.length > 0 ? (
props.url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
);
}
can you please confirm the results?
Try this and tell me if it works
import React, { useEffect, useState } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const response = await fetch(
"https://mysiteproject-8adcf.firebaseio.com/products.json"
);
const responseJson = await response.json();
console.log(responseJson);
setUrl(responseJson);
}
asyncCall();
}, []);
return (
<div>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</div>
);
}
I almost solved the problem with this implementation
function Bags() {
const [url, setUrl] = useState([]);
//const [myfinal,setFinal] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("https://mysiteproject-8adcf.firebaseio.com/products.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
if (url) {
//let myvar = url;
//console.log(myvar.img);
//console.log(myvar);
url.map((url) => console.log(url.img));
}
//console.log()
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{url.length > 0 ? (
url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
)
}
I don't know if this implementation is optimal
Related
If you look to my JSX I am trying to create sizes from the product object the problem is when I loop throguh the product.size.map() it threw an error that says product is undefined.
How to solve this?
update
I was able to do some workaround but I am sure it is not considered as a best practice.
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match])
if(Object.keys(product).length===0) return null // this is the workaround I have done what do you think?
else{
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{product.size.map(s=><Button key={s} className='p-2 m-1'>{s}</Button>)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}}
export default ProductScreen
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match,product ])
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{console.log(product)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}
export default ProductScreen
You can't prevent the initial state being rendered.
(I've added an update to the bottom related to the updated question.)
If that component can't be usefully rendered without the product, then product should be a prop it receives, not a state member it fetches. The fetch should be in the parent component.
But if you want the fetch to remain in the component, you need to set a marker value (like null) and branch your rendering based on that marker value, e.g.:
if (!product) {
return /* ...render a "loading" message... */;
}
// ..render `product` here...
Re your edited question: You seem to be having an issue with product.size.map(/*...*/). If we look at your initial state, you have:
const [product, setProduct] = useState({});
Since it doesn't have a size property, product.size is undefined and product.size.map will fail with an error about product.size being undefined.
You haven't shown the code trying to do the product.size.map, but you have various options.
You could use a guard:
{product.size && product.size.map(/*...*/)}
You could include an empty array in the initial state:
const [product, setProduct] = useState({size: []});
Remove product from the dependencies of useEffect as that will trigger the callback and causes an infinite loop.
useEffect(() => {
...
},[match])
To not render anything until product is fetched, use a falsy value like undefined/null as the initial state:
const [product , setProduct] = useState();
and check product before returing the JSX:
if(!product) return null;
return (
<>
<Link className='btn btn-light my-3' to='/'>
...
)
My backend works perfectly fine, even my redux integration with react js work completely fine but as soon as i use useEffect it gives an error of "Cannot read properties of undefined"
Below is my code:
const ProductListScreen = ({ match }) => {
const dispatch = useDispatch()
const shopDetails = useSelector((state) => state.shopDetails)
const { loading, error, shop } = shopDetails
useEffect(() => {
dispatch(shopDetail(match.params.shopid))
}, [dispatch, match])
if (loading) {
return (
<div className='loader'>
<Loader />
</div>
)
}
return (
<>
<Link className='btn btn-dark my-3' to='/'>
Go Back
</Link>
<h3>{shop.name} Products</h3>
{error ? (
<Message variant='warning' color='red'>
{error}
</Message>
) : (
<Row>
{shop &&
shop.products.map((product) => (
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} shopId={shop._id} />
</Col>
))}
</Row>
)}
</>
)
}
<Row>
{shop &&
shop.products?.map((product) => (
<Col key={product._id} sm={12} md={6} lg={4} xl={3}>
<Product product={product} shopId={shop._id} />
</Col>
))}
</Row>
Use the optional chaining operator in order to skip map of undefined products
Pretty sure, shop it's truthy, so the condition is true, but still you are not checking the truthiness of products when mapping through it
how do I set the number of cards per row? I need 4 cards to be in one row of mine.
I have been trying for hours.. can't figure out what i do wrong..
I was getting all sorts of compilation errors when i played around with this.
thanks!
:)
import { React } from 'react';
import { Card, Button, CardGroup } from 'react-bootstrap';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const MyDishes = props => {
const { dishes } = props;
return (
<CardGroup style={{display: 'flex', flexDirection: 'row'}}>
{dishes.length > 0 &&
dishes.map(d => (
<Row xs={1} md={2} className="g-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col>
<Card key={d.id} style={{ width: '100em' }} style={{flex: 1}}>
<Card.Img variant="top" src={d.attributes.picture} />
<Card.Body>
<Card.Title>{d.attributes.name}</Card.Title>
<Card.Text>Possibly some text here</Card.Text>
<Link to={`/dishes/${d.id}`}>
<Button variant="primary">Full Recipe</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
</CardGroup>
);
};
const mapStateToProps = state => {
return {
dishes: state.myDishes
};
};
export default connect(mapStateToProps)(MyDishes);
Above bootstrap 5
<Row className="row-cols-4">
...
</Row>
or any version
<Row className="row-cols-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col className="col-3">
...
</Col>
</Row>
I map through data array fetched from API.
I have an array (speciality) inside the data array. but info is not rendered. no errors
const CardItem = ({ data = [] }) => {
console.log(data, 'mapping')
return (
<>
{ data.map(item => {
return (
<Col lg={4} md={6} sm={12} xs={12} key={item.id}>
<Card>
<Card.Img variant="top" src={item.avatar} />
<Card.Body>
<Card.Title>{item.firstName} {item.lastName}</Card.Title>
<Card.Text>
<ul>
{Array.isArray(item.specialty) && item.speciality.length > 0 && item.speciality.map((i) => {
return (
<li>{i}</li>
)
}) : null}
</ul>
</Card.Text>
<Button variant="primary">Go somewhere</Button>
</Card.Body>
</Card>
</Col>
)
})}
</>
)
}
export default CardItem
To explicitly check for type Array you can check with Array.isArray(var)
{Array.isArray(item.specialty) && item.speciality.length > 0 ? item.speciality.map((i) => {
return (
<li>{i}</li>
)
}) : null}
I am trying to achieve a behavior on click. What I want is to have the button show “Click to close” when clicked, and then once you click again - revert back to its initial state (showing ‘Easy Riddles’).
Here is a snippet of my code:
import React, { useState } from "react";
import { Accordion, Card, Button, Container, Row, Col } from "react-bootstrap";
const Riddles = (props) => {
const levelStatus = {
easy: "Easy Riddles",
medium: "Intermediate Riddles",
hard: "Hard Riddles",
};
const collapseButton = "Click to close";
const [close, setClose] = useState({
easy: false,
medium: false,
hard: false,
});
// Handle click
const handleClick = (e) => {
setClose({
close.easy: true
});
};
return (
<>
<div className="riddlesection">
<Container>
<Row>
<Col className="riddlegrid" xs={12} sm={12} md={4}>
<Accordion>
<Card id="easy">
<Card.Header>
<Accordion.Toggle
id="easy"
onClick={handleClick}
value="Easy Riddles"
as={Button}
variant="link"
eventKey="0"
>
{close.easy ? levelStatus.easy : collapseButton}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Row>
<Col xs={6} sm={6} md={6}>
Countdown
</Col>
<Col className="resetlink" xs={6} sm={6} md={6}>
Switch
</Col>
</Row>
<div>Hello! I'm the body</div>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Col>
</Row>
</Container>
</div>
</>
);
};
What can I do to achieve differently the behavior that I want?
you need to update the state as below
const handleClick = (e) => {
setClose(prevCloseState => {
...prevCloseState,
easy: true
})
};