react render using map on Grid - reactjs

I'm trying to build a page that shows the weather in all the locations mentioned in a list.
I want to render a card for each location and sort the cards next to each other so every row will contain 5 location cards [][][][][],
currently, it renders only one under the other in a column:
how can I solve this?
(weather.favoritesWeather is a list that contains all the data which needs).
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<div>
<Row className="justify-content-md-center">
<Col md={3} >
<SmallConditionsCard data={favoriteLocation} />
</Col>
</Row>
</div>
)
);
return <div>
{FavoriteWeatherCards}
</div>;
};
code :
const SmallConditionsCard = ({data}) => {
const { location, weather } = data;
let history = useHistory();
const handleClick = () => {
history.push('/');
};
return (
<div>
<Card>
<CardHeader
sx={{ background: 'ghostwhite' }}
title={
<Typography align="center" variant="h5">
{location.name}
</Typography>
}
/>
<CardContent sx={{ textAlign: 'center' }}>
<WeatherIcon
number={weather[0].WeatherIcon}
xs={12}
sx={{ maxHeight: 200, maxWidth: 200 }}
/>
<Typography variant="h6">{weather[0].WeatherText}</Typography>
<Typography variant="p">
{formatTemp(weather[0].Temperature.Metric.Value, celcius)}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={handleClick}>Learn More </Button>
</CardActions>
</Card>
</div>
);
};
this is the result now:

If you are using react-bootstrap you need your container
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<Row className="justify-content-md-center">
<Col>
<SmallConditionsCard data={favoriteLocation} />
</Col>
</Row>
)
);
return <Container>
{FavoriteWeatherCards}
</Container>;
};
In case that you want to render something using material UI grid could be something like this
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<Grid item xs={3}>
<Item>
<SmallConditionsCard data={favoriteLocation} />
</item>
</Row>
</Grid>
)
);
return <Grid container spacing={2}>
{FavoriteWeatherCards}
</grid>;
};

Related

Using react hook forms with dynamically added components

I have some components with the same set of input fields. User could add them or remove them.
const [sectionItems, setActionItems] = useState<ISectionItems[]>([
{id: Date.now(), number: 1}
])
{sectionItems.map((sectionItem) =>
<SectionItem
key={sectionItem.id}
number={sectionItem.number}
addSectionItem={addSectionItem}
removeSectionItem={removeSectionItem}
isOnlySection={isOnlySection}
/>)}
So the fiels are inside each SectionItem component.
I use react-hook-forms to get the data from the form, but I have no idea how to deal with that in the situation when we have dynamically changing list of components
const SectionItem: FC<SectionItemProps> = (SectionItemProps) => {
const addClickHandler = (e: React.MouseEvent) => {
SectionItemProps.addSectionItem()
}
const removeClickHandler = (e: React.MouseEvent) => {
SectionItemProps.removeSectionItem(SectionItemProps.number)
}
return (
<React.Fragment>
<Typography variant="h6" gutterBottom sx={{marginTop: 3}}>
{`Участок № ${SectionItemProps.number}`}
</Typography>
<Grid container spacing={3} sx={{marginTop: 1}}>
<Grid item xs={12} sm={6}>
<TextField
required
id="insideDiameter"
name="insideDiameter"
fullWidth
variant="standard"
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
required
id="outsideDiameter"
name="outsideDiameter"
fullWidth
variant="standard"
/>
</Grid>
<Grid container spacing={3} sx={{marginTop: 1}}>
<Grid item xs={12} sm={12} style={{textAlign: "center"}}>
<Fab size="small" aria-label="add" >
<AddIcon color = 'primary' onClick={addClickHandler}/>
</Fab>
{SectionItemProps.isOnlySection
? <></>
: <Fab size="small" aria-label="add" sx={{marginLeft: 1}}>
<RemoveIcon color='primary' onClick={removeClickHandler} />
</Fab>
}
</Grid>
</Grid>
</React.Fragment>
);

How can i delete width in react - bootstrap class?

I'm doing an internet-store, and i have a problem with top panel. I tried everything to fix it, but only when i change class row in dev tools i can get result.
row>* {
flex-shrink: 0;
width: 100%;
max-width: 100%;
padding-right: calc(var(--bs-gutter-x) * .5);
padding-left: calc(var(--bs-gutter-x) * .5);
margin-top: var(--bs-gutter-y);
}
i need to delete width from this class, but i don't know how to do it. If u can help me, it will be cool.
oh, if i replace component for nothing change.
<Container>
<Row className="mt-2">
<Col md={3}>
<TypeBar />
</Col>
<Col md={9} >
<BrandBar />
<DeviceList />
</Col>
</Row>
</Container>
const BrandBar = observer(() => {
const {device} = useContext(Context);
return (
<Row className="d-flex">
{device.brands.map(brand =>
<Card
style={{cursor: 'pointer'}}
key={brand.id}
className='p-2 align-items-center'
onClick={() => device.setSelectedBrand(brand)}
border={brand.id === device.selectedBrand.id ? 'danger' : 'light'}
>
{brand.name}
</Card>
)}
</Row>
)
})
When using bootstrap's containers always follows the Container > Row > Col order.
The class row>* is intended to select the cols, but instead is selecting your card.
Try doing like so
const BrandBar = observer(() => {
const { device } = useContext(Context);
return (
//Add a Container here
<Container>
<Row className="d-flex">
{device.brands.map((brand) => (
//Add a Col here
<Col>
<Card
style={{ cursor: 'pointer' }}
key={brand.id}
className="p-2 align-items-center"
onClick={() => device.setSelectedBrand(brand)}
border={brand.id === device.selectedBrand.id ? 'danger' : 'light'}
>
{brand.name}
</Card>
</Col>
))}
</Row>
</Container>
);
});

How to target single item in list with onClick when mapping in ReactJS?

My react component returns data from my Firestore DB and maps the data it on Material-UI cards. However, when I press the ExpandMoreIcon, it opens EVERY card. I just want to open each card individually. I know the solution has to do with useState function for expanded & setExpanded.
I've tried to fix this bug but I cant seem to make it work. Any help would be greatly appreciated.
export const NFTprojects = () => {
const [expanded, setExpanded] = useState(false);
const handleExpandClick = (id) => {
setExpanded(!expanded)
};
const [projects, setProjects] = useState([]);
const ref = firebase.firestore().collection("NFTprojects");
function getProjects() {
ref.onSnapshot((querySnapshot) => {
const items = []; //initiliaze empty array
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setProjects(items);
});
}
useEffect(() => {
getProjects();
}, []);
return (
<div>
<Grid container spacing={4} direction="row" justifyContent="flex-start" alignItems="flex-start">
{projects.map((project) => (
<Grid item xs={4}>
<Card sx={{ maxWidth: 400, borderRadius: 3, mb: 5 }}>
<CardMedia
component="img"
height="140"
image={project.imageUrl}
alt={project.projectName}
/>
<CardContent>
<Typography gutterBottom variant="h5" sx={{ fontWeight: 'bold' }}>
{project.projectName}
</Typography>
<Typography variant="h6" gutterBottom component="div" fontWeight="bold">
{project.jobPosition}
</Typography>
<Typography variant="body2" color="text.secondary" style={{ fontFamily: 'Merriweather' }}>
{project.projectDesc}
</Typography>
</CardContent>
<CardActions disableSpacing>
<Tooltip title="Website">
<IconButton aria-label="secondary marketplace" href={project.websiteLink} target="_blank">
<WebsiteIcon />
</IconButton>
</Tooltip>
<Tooltip title="Twitter">
<IconButton aria-label="twitter" href={project.twitterLink} target="_blank">
<TwitterIcon />
</IconButton>
</Tooltip>
<Tooltip title="Secondary">
<IconButton aria-label="Secondary market link" href={project.secondaryMarket} target="_blank">
<ShoppingCartIcon />
</IconButton>
</Tooltip>
<Tooltip title="Discord">
<IconButton aria-label="discord" href={project.discordLink} target="_blank">
<SvgIcon component={DiscordIcon} viewBox="0 0 600 476.6" />
</IconButton>
</Tooltip>
<Button size="small" variant="contained" sx={{ ml: 15, backgroundColor: 'black' }}>Apply</Button>
<ExpandMore
expand={expanded}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: 'bold' }} style={{ fontFamily: 'Merriweather' }}>Job Description:</Typography>
<Typography paragraph>
{project.jobDesc}
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Prerequisites</Typography>
<Typography paragraph>
{project.jobPrereq}
</Typography>
</CardContent>
</Collapse>
</Card>
</Grid>
))}
</Grid>
</div >
);
}
One approach is to create a separate component for the card. This will enable you to add states to the component and control them. Here is a minimal example demonstrating how you can approach it.
import React, { useState } from "react";
// this is just sample data to work with - equivalent to the data you get from Firebase
const sampleCardsArray = [
{
id: 0,
name: "Card 1",
color: "red",
description: "This is card 1",
},
{
id: 1,
name: "Card 2",
color: "blue",
description: "This is card 2",
},
{
id: 2,
name: "Card 3",
color: "green",
description: "This is card 3",
},
];
// component for all cards
export const AllCards = () => {
// this state is used to store the INDEX of the card that is currently expanded
const [expandedCard, setExpandedCard] = useState(null);
return (
<div>
{sampleCardsArray.map((card, index) => (
<OneCard
card={card}
key={card.id}
// this prop passes the boolean value of whether the card is expanded or not
isExpanded={expandedCard === index}
// this prop receives the index of the card that is expanded and sets the state
expandCard={() => setExpandedCard(index)}
/>
))}
</div>
);
};
// component for one card
// We only show the fields: name and color. We show the description when the card is clicked
export const OneCard = ({ card, isExpanded, expandCard }) => {
return (
<div>
<h1>{card.name}</h1>
<h2>{card.color}</h2>
{
// showing expand button only when card is not expanded
}
{isExpanded ? (
<p>{card.description}</p>
) : (
<button onClick={() => expandCard()}>Expand card</button>
)}
</div>
);
};

Fetch API with Axios in React

const key = '618479acc2ef5ba8018516ac'
function UserPost1 () {
const [post, setPost] = useState(['']);
const handleExpandClick = () => {
setExpanded(!expanded);
};
useEffect(() =>{
axios.get('https://dummyapi.io/data/v1/post' , { headers: { 'app-id': key } })
.then(res => {
setPost(res.data.data)
console.log(res)
})
.catch(err =>{
console.log(err)
})
},[]);
return(
<div className="Post-style">
{post.map(post =>(
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355}} style={{backgroundColor: "aquamarine"}} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
))}
</div>
)
}
export default UserPost1;
When im run this code i cant get the data from API using Axios, it says error cannot read properties of undefined (reading 'picture'). I tried to catch the error but it does not show in console log. How do i solve this problem.
should i make the axios to wait until it gets the data API or make it to false.
What should i do, its my first time with API.
Somehow, the picture property is missing inside the owner object.
Add optional chaining before picture:
<CardHeader
avatar={
<Avatar
src={post.owner?.picture}
/>
}
...
Render your post component after complete async query and set state value. Use condition rendering and change default state value from [''] to null.
If your component has a specific height and width, then create a placeholder with the same values.
To exclude layout jumps during query execution, show a placeholder. Show the placeholder while the request is running, and after the request is complete and the value is set to the state, show the post component. Good luck!
// change default useState value
const [post, setPost] = useState(null);
// add condition rendering
{post ? post.map(post => (
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355 }} style={{ backgroundColor: "aquamarine" }} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
)) : <div> PLACEHOLDER </div>}

React Router does not switch pages

I have a search bar and when I type in smth I see fetch movie list. then I click on the details button to see a description of according film, url changes but new component does not render. IDK why. Just when I click on according card item and as soon as I refresh the page it shows me the rendered Details page. Another issue that I receive props and param (imdbID) How to show all the details that were passed by props - {films}
function App() {
const [searchText, setSearchText] = useState('')
const [films, setFilms] = useState([])
const url = `http://www.omdbapi.com/?i=tt7286456&apikey=KEY&s=${searchText}`
const onTextChange = async (e) => {
setSearchText(e.target.value)
const res = await axios.get(url)
setFilms(res.data.Search)
console.log(films)
}
const history = createMemoryHistory()
return (
<>
<Router>
<Container>
<h1 className='mt-4'>MovieStore</h1>
<Row>
<input
style={{width: '90%', margin: '0 auto'}}
type='text'
placeholder='Try look for harry... or whatever film you like...'
name="searchText"
onChange={onTextChange}
value={searchText}
className='mt-4 mb-4'
/>
</Row>
<Row style={{color: "#000"}}>
{ films?.map(item => {
return (
<Col lg={3} md={3} sm={12} key={item.imdbID} >
<Card style={{height: 'calc(100% - 10px)' }}>
<Card.Img variant="top" src={item["Poster"]} style={{ objectFit: 'cover' }}/>
<Card.Body>
<Card.Title>{item["Title"]}</Card.Title>
<Card.Text>
{item["Year"]}
</Card.Text>
<Link to={`/film/${item.imdbID}`}><Button variant="primary">Details</Button></Link>
</Card.Body>
</Card>
</Col>
)
})}
</Row>
</Container>
<Route exact path='/film/:imdbID' render={(props) => <DetailPage films={films} {...props} />}/>
</Router>
</>
);
}
Here is my details page
const DetailsPage = ({films}) => {
const history = useHistory();
const location = useLocation();
const { id } = useParams()
console.log('props', id)
return (
<Container>
<Row>
<Col>
<Card style={{ width: '50%' }} className='mt-4'>
<Card.Body>
<Card.Title>Movie title: {id}</Card.Title>
<Card.Text>
</Card.Text>
<Button style={{background: '#CE0A03', border: 'none' }} variant="primary" onClick={() => history.goBack() }>Go on main page</Button>
</Card.Body>
</Card>
</Col>
</Row>
</Container>
)
}
If you want to render this component and hide other then use the switch in react-router.
<Router>
<Switch>
<Route exact path='/'>
<Container>
<h1 className='mt-4'>MovieStore</h1>
<Row>
<input
style={{width: '90%', margin: '0 auto'}}
type='text'
placeholder='Try look for harry... or whatever film you like...'
name="searchText"
onChange={onTextChange}
value={searchText}
className='mt-4 mb-4'
/>
</Row>
<Row style={{color: "#000"}}>
{ films?.map(item => {
return (
<Col lg={3} md={3} sm={12} key={item.imdbID} >
<Card style={{height: 'calc(100% - 10px)' }}>
<Card.Img variant="top" src={item["Poster"]} style={{ objectFit: 'cover' }}/>
<Card.Body>
<Card.Title>{item["Title"]}</Card.Title>
<Card.Text>
{item["Year"]}
</Card.Text>
<Link to={`/film/${item.imdbID}`}><Button variant="primary">Details</Button></Link>
</Card.Body>
</Card>
</Col>
)
})}
</Row>
</Container>
</Route>
<Route exact path='/film/:imdbID' render={(props) => <DetailPage films={films} {...props} />}/>
<Switch>
</Router>

Resources