Grid of card in material-ui react - reactjs

Hello I'm trying to create that I had three cards in a row right now one after the other
Can't find a solution

It's a complement
const Posts = ({ setCurrentId }) => {
const fuzzySearch = (list, searchValue) => {
let buf = ".*" + searchValue.replace(/(.)/g, "$1.*").toLowerCase();
var reg = new RegExp(buf);
let newList = list.filter(function (e) {
return reg.test(e.title.toLowerCase()&&e.message);
});
return newList;
};
const [searchValue, setSearchValue] = useState("");
const posts = useSelector((state) => state.posts);
const classes = useStyles();
const [pageNumber, setPageNumber] = useState(1);
const [buttonnext, setbuttonnext] = useState(false);
const [prebutton, setprebutton] = useState(true);
const limit=8;
const [startIndex,setstartIndex] = useState();
const [endIndex,setendIndex] = useState();
useEffect(()=>{
setstartIndex((pageNumber-1)*limit)
setendIndex(pageNumber*limit)
console.log(startIndex)
console.log(endIndex)
},[posts,pageNumber])
const Next = ()=>{
if(pageNumber === (Math.floor((posts.length+limit -1)/limit))){
setbuttonnext(true)
}else{
setPageNumber(pageNumber+1)
setprebutton(false)
}
}
const Previous = () =>{
if(pageNumber === 1){
setprebutton(true)
}else{
setPageNumber(pageNumber-1)
setbuttonnext(false)
}
}
return(
!posts.length ? <CircularProgress /> : <>
< >
<AppBar className={classes.appBar} position="static" color="inherit">
<Typography className={classes.heading} variant="h2" >קבוצות אחרונות</Typography>
<Paper component="form" className={classes.root}>
<IconButton className={classes.iconButton} aria-label="menu">
</IconButton>
<InputBase
className={classes.input}
placeholder="חיפוש "
inputProps={{ 'aria-label': 'search ' }}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
/>
<IconButton className={classes.iconButton} aria-label="search">
<SearchIcon />
</IconButton>
<Divider className={classes.divider} orientation="vertical" />
</Paper>
</AppBar>
<ol>
{fuzzySearch(posts, searchValue).slice(startIndex,endIndex).map((d) => (
<Grid key={d._id} item xs={10} sm={6} md={6}>
<Grow in>
<Post post={d} setCurrentId={setCurrentId} />
</Grow>
</Grid>) )}
</ol>
</>
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
<Grid container direction="row-reverse"justifyContent="center"alignItems="flex-end" >
<>
<button className={classes.button} onClick={Next}>הבא</button>
<p className={classes.numText}>{ pageNumber}</p>
<Button className={classes.button} onClick={Previous}>אחורה</Button>
</>
</Grid>
</Grid></>
)
};
export default Posts;
Then called to app in app.js Grid in shape
return (
<Container maxWidth="lg" >
<Grow in>
<Container>
<Posts setCurrentId={setCurrentId} />
</Container>
</Grow>
</Container>
);
};
export default App;
I would to create a grid that I had three in a row

I don't know what you are trying to achieve, but I assume that you are trying to display 3 cards in a row. The idea is that Material-UI is using flexbox, and they are divided into 12 equal columns.
<Grid spacing={2}>
// Define grid for card items
{
data.map((dataItem) => (
<Grid key={dataItem.id}>
// ... items inside
</Grid>
))
}
</Grid>

Related

React : Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement

I never see this error before.
I found where error is occurred.
However, i don't know why it occurred.
there is no return code before setState. If i use {imgPath} only, there is no error occurred.
What I do
I create another state for test. If i use state into component, error occurred. It is line 86.
this is my full code
I delete import component code
import { FC, useRef, useState } from 'react';
import styled from 'styled-components';
import { 팀정보변경, uploadLogo } from 'controller/modifyTeam';
import { getTeamInformation } from 'controller/team';
// module
import { useSnackbarStore } from 'module/store/snackbar';
import { useTeamStore } from 'module/store/team';
const Form: FC<Props> = ({ teamname, timezone, id, name, url }) => {
const { updateTeam } = useTeamStore();
const { pushSnackbar } = useSnackbarStore();
const [isEmpty, setIsEmpty] = useState<boolean>(false);
const [imgPath, setImgPath] = useState<string>('');
const [test, setTest] = useState<string>('');
// ref
const teamNameRef = useRef<HTMLInputElement>(null);
const timeLineRef = useRef<HTMLInputElement>(null);
const imgRef = useRef<HTMLInputElement>(null);
const widthSX = (width: string) =>
useSX({
width: `${width}`,
});
const paddingSX = useSX({
padding: '4px 12px !important',
});
// formData
const onClickUploadImage: () => void = () => imgRef?.current?.click();
const onDeleteImage: () => void = () => setImgPath('');
const updateTeamInformation: () => void = async () => {
const res = await getTeamInformation();
updateTeam(res.data.result);
};
const onSubmit: () => void = async () => {
const timeline = timeLineRef.current.value.slice(4, 10).replaceAll(':', '');
const res = await 팀정보변경(teamNameRef.current.value, timeline, imgPath, id);
if (res.data.msg === 'created') {
await updateTeamInformation();
pushSnackbar({
id: 'teamworthchanged',
type: 'Info',
title: `${t('snackbar.team_worth_changed')}`,
});
}
};
const onChangeImage: (event: any) => void = async (event: any) => {
const formData = new FormData();
formData.append('logo', event.target.files[0]);
const res = await uploadLogo(formData);
setImgPath(res.data.result.path);
};
return (
<>
<SideBar index={1} />
<Container>
<Contents xs={12} sm={8} md={6} lg={5}>
<Font className="h5">{t('nav.menu.settings.basic')}</Font>
<WhiteSpace />
<Grid container item xs={12} direction="column">
{/* 기본설정 */}
<Font className="body-2">{t('settings.basic.profile.heading')}</Font>
<Font className="caption">{t('settings.basic.profile.sub')}</Font>
<Grid container flexWrap="wrap" justifyContent="space-between">
<WhiteSpace />
<Grid xs={4} item container alignItems="center">
{imgPath === '' ? (
url === null ? (
<DefaultImg
container
justifyContent="center"
alignItems="center"
sx={widthSX('80px')}
>
{name?.slice(0, 1)}
</DefaultImg>
) : (
<TeamIcon src={url} alt="팀 로고" />
)
) : (
<TeamIcon src={imgPath} alt={'팀 로고'} />
)}
</Grid>
{/* 이미지 업로드 */}
<Grid container item xs="auto" alignItems="center" flexWrap="wrap" sx={UploadImageSX}>
<label htmlFor="contained-button-file">
<MuiButton
content={
<Grid container alignItems="center">
<Grid item>{t('btn.upload_image')}</Grid>
<Grid item>
<MdiIcon width={16} height={16} src="/icons/upload/ic_upload_white.svg" />
</Grid>
</Grid>
}
size="small"
type="contained"
sx={paddingSX}
onClick={onClickUploadImage}
/>
</label>
<ImageInput
accept="image/*"
id="contained-button-file"
onChange={onChangeImage}
type="file"
ref={imgRef}
/>
{/* 이미지 삭제 버튼 */}
<MuiButton
content={t('btn.remove')}
size="small"
color={palette.gray3}
sx={paddingSX}
onClick={onDeleteImage}
/>
</Grid>
</Grid>
</Grid>
<WhiteSpace />
{/* 팀이름 INPUT */}
<Input
label="common.team_name"
isEmpty={isEmpty}
setIsEmpty={setIsEmpty}
ref={teamNameRef}
value={teamname}
/>
<WhiteSpace />
{/* 시간대 */}
<Grid container item xs={12} direction="column">
<Font className="body-2">{t('settings.basic.timezone.heading')}</Font>
<Font className="caption">{t('settings.basic.timezone.sub')}</Font>
</Grid>
<WhiteSpace />
{/* <TimeLine currentTime={timezone} time={time} setTime={setTime} /> */}
<Timeline ref={timeLineRef} time={timezone} />
<WhiteSpace />
<WhiteSpace />
<MuiButton
content={t('btn.save_changes')}
sx={widthSX('100%')}
type="contained"
onClick={onSubmit}
/>
</Contents>
</Container>
</>
);
};
export default Form;

Warning: Each child in a list should have a unique "key" prop; Check the render method of `Post`

when I click by tag, it works fine, but when I click creators, it just return "No posts".
I check the console, and there's an error says the key in list should be unique. Why this happens? Since searchbytag works, does this mean the render method of 'Post' is fine?
updated code
import axios from 'axios';
const API = axios.create({ baseURL: 'http://localhost:5000' });
API.interceptors.request.use((req) => {
if (localStorage.getItem('profile')) {
req.headers.Authorization = `Bearer
${JSON.parse(localStorage.getItem('profile')).token}`;
}
return req;
});
export const createPost = (newPost) => API.post('/posts', newPost);
const Post = ({ post, setCurrentId }) => {
const user = JSON.parse(localStorage.getItem('profile'));
const [likes, setLikes] = useState(post?.likes);
const dispatch = useDispatch();
const history = useHistory();
const classes = useStyles();
const userId = user?.result?._id;
const hasLikedPost = post.likes.find((like) => like === userId);
const handleLike = async () => {
dispatch(likePost(post._id));
if (hasLikedPost) {
setLikes(post.likes.filter((id) => id !== userId));
} else {
setLikes([...post.likes, userId]);
}
};
const Likes = () => {
if (likes.length > 0) {
return likes.find((like) => like === userId)
? (
<><ThumbUpAltIcon fontSize="small" /> {likes.length > 2 ? `You and ${likes.length - 1} others` : `${likes.length} like${likes.length > 1 ? 's' : ''}`}</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {likes.length} {likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
const openPost = (e) => {
// dispatch(getPost(post._id, history));
history.push(`/posts/${post._id}`);
};
return (
<Card className={classes.card} raised elevation={6}>
<ButtonBase
component="span"
name="test"
className={classes.cardAction}
onClick={openPost}
>
<CardMedia className={classes.media} image={post.selectedFile || 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={post.title} />
<div className={classes.overlay}>
<Typography variant="h6">{post.name}</Typography>
<Typography variant="body2">{moment(post.createdAt).fromNow()}</Typography>
</div>
{(user?.result?._id === post?.creator) && (
<div className={classes.overlay2} name="edit">
<Button
onClick={(e) => {
e.stopPropagation();
setCurrentId(post._id);
}}
style={{ color: 'white' }}
size="small"
>
<MoreHorizIcon fontSize="default" />
</Button>
</div>
)}
<div className={classes.details}>
<Typography variant="body2" color="textSecondary" component="h2">{post.tags.map((tag) => `#${tag} `)}</Typography>
</div>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{post.title}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{post.message.split(' ').splice(0, 20).join(' ')}...</Typography>
</CardContent>
</ButtonBase>
<CardActions className={classes.cardActions}>
<Button size="small" color="primary" disabled={!user?.result} onClick={handleLike}>
<Likes />
</Button>
{(user?.result?._id === post?.creator) && (
<Button size="small" color="secondary" onClick={() => dispatch(deletePost(post._id))}>
<DeleteIcon fontSize="small" /> Delete
</Button>
)}
</CardActions>
</Card>
);
};
export default Post;
Here's my code
const CreatorOrTag = () => {
const { name } = useParams();
const dispatch = useDispatch();
const { posts, isLoading } = useSelector((state) => state.posts);
const location = useLocation();
useEffect(() => {
if (location.pathname.startsWith('/creators')) {
dispatch(getPostsByCreator({ name: name }));
}
else {
dispatch(getPostsBySearch({ tags: name }));
}
}, []);
if (!posts.length && !isLoading) return 'No posts';
return (
<div>
<Typography variant="h2">{name}</Typography>
<Divider style={{ margin: '20px 0 50px 0' }} />
{isLoading ? <CircularProgress /> : (
<Grid container alignItems="stretch" spacing={3}>
{posts?.map((post) => (
<Grid key={post._id} item xs={12} sm={12} md={6} lg={3}>
<Post post={post} />
</Grid>
))}
</Grid>
)}
</div>
);
};
export default CreatorOrTag;
What this warning indicates is that the value you're providing for key is the same in at least two items of the array. I'd put money on the _id property for at least two of them being null or undefined.

React Material UI Cards w/Modal

I'm trying to configure this React JS / Material UI modal dialog so that when the user clicks on the card, it opens a corresponding full-sized image (with title and subtitle). Data for each card is mapped from a JSON file (via AXIOS).
I can get the modal window to open, but it is showing all of the card images in the modal and they are stacked on top of each other. The console.log("SELECTED CAMPAIGN: ", selectedCampaign) code inside the handleOpen() function is one click behind...it actually logs the object that was selected prior to the current click event.
I'm relatively new to functional components and hooks, so I know that I am making simple and fundamental mistakes...please help me figure out the proper way to set this up:
const CampaignItems = ({campaigns, loading}) => {
const classes = useStyles();
const [open, setOpen] = useState(false);
const [selectedCampaign, setSelectedCampaign] = useState();
const handleOpen = (campaign) => {
setSelectedCampaign(campaign);
console.log("SELECTED CAMPAIGN: ", selectedCampaign);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
..................
<div>
<GridContainer>
{campaigns && search(campaigns).map((campaign) => (
<GridItem key={campaign.id} xs={12} sm={6} md={4}>
<Card className={classes.root}>
<CardActionArea>
<CardMedia
component="img"
alt={campaign.alt}
height="140"
image={campaign.image}
title={campaign.title}
onClick={() => handleOpen(campaign)}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{campaign.title}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{campaign.subtitle}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<IconButton
size="medium"
color="primary"
aria-label="More Information"
onClick={() => handleOpen(campaign)}
>
<InfoIcon />
</IconButton>
<Modal
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500
}}
>
<Fade
in={open}
>
<div className={classes.paper}>
<h6>{campaign.title}</h6>
<p>{campaign.subtitle}</p>
<img src={campaign.image} />
</div>
</Fade>
</Modal>
</CardActions>
</Card>
</GridItem>
))}
</GridContainer>
</div>
..................
It seems your issue is that you use only a single open state to trigger your modals to open, so they are all triggered open concurrently.
I suggest to instead using the selectedCampaign of the card you're interacting with and use the campaign id that to match which modal to open.
const CampaignItems = ({campaigns, loading}) => {
...
const [selectedCampaign, setSelectedCampaign] = useState(null);
const handleOpen = (campaign) => () => {
setSelectedCampaign(selectedCampaign =>
selectedCampaign.id === campaign.id ? null : campaign
);
};
const handleClose = () => {
setSelectedCampaign(null);
};
...
<div>
<GridContainer>
{campaigns && search(campaigns).map((campaign) => (
<GridItem key={campaign.id} xs={12} sm={6} md={4}>
<Card className={classes.root}>
<CardActionArea>
<CardMedia
...
onClick={handleOpen(campaign)}
/>
...
</CardActionArea>
<CardActions>
<IconButton
...
onClick={handleOpen(campaign)}
>
...
</IconButton>
<Modal
...
open={selectedCampaign.id === campaign.id} // <-- check id match
onClose={handleClose}
...
>
...
</Modal>
</CardActions>
</Card>
</GridItem>
))}
</GridContainer>
</div>
...

React search by name

Hello I am currently fetching through an API call to search by name in my search bar. I'm having an error of whenever I type a string in my search bar.
This is my postman URL /product/list/search?search=assorted where I can fetch names with "assorted"
I'm fetching it in frontend like this
export const searchProducts = async (query) => {
return await get(`product/list/search=?${query})
}
and this is how I implement it on my search page
const SearchPage = () => {
const [products, setProducts] = useState([])
const [query, setQuery] = useState("")
useEffect(() => {
getAllProducts().then((products) => {
setProducts(products)
})
}, [])
useEffect(() => {
const loadProducts = async () => {
const query = await searchProducts(query)
setQuery(query)
}
loadProducts()
}, [query])
return (
<>
<Grid container spacing={3}>
<Grid item xs={1}>
<Box pt={1.5}>
<Link to="#">
<ArrowBackIcon className={classes.backSize} />
</Link>
</Box>
</Grid>
<Grid item xs={11}>
<Paper className={classes.root}>
<IconButton aria-label="menu"></IconButton>
<InputBase
className={classes.input}
placeholder="Search for foods"
onChange={(e) => setQuery(e.target.value)}
value={query}
/>
<IconButton aria-label="search">
<SearchIcon />
</IconButton>
</Paper>
</Grid>
</Grid>
<Box pt={1}>
<CategoryList categories={categories} />
</Box>
<Box pt={1}>
<ProductList products={products} />
</Box>
</>
)
}
Did you try with / at the beginning?
export const searchProducts = async (query) => {
return await get(`/product/list/search?search=${query}`)
}
From what i can see your postman api call and the one in the await doesn't seems to be matching - it should be
await get(`product/list/search?search=${query}`);

Reactjs tests (react testing library)

Can anyone help with how I can test the component below? I am using the testing-library / react library and am having difficulties.
export default function BodyCardConfirmacaoSeguranca({ email, celular }) {
const [selectedCard, setSelectedCard] = useState(null);
const handleSelectCard = (value) => {
if (value === selectedCard) {
setSelectedCard(null);
} else {
setSelectedCard(value);
}
};
return (
<>
<CardEnvioCod
data-testid="email"
tipoEnvio="email"
data={email}
handleSelectCard={() => handleSelectCard('email')}
selectedCard={selectedCard}
/>
<CardEnvioCod
data-testid="sms"
tipoEnvio="sms"
data={telefone(celular)}
handleSelectCard={() => handleSelectCard('sms')}
selectedCard={selectedCard}
/>
</>
);
}
I'm trying something like this:
it('', () => {
const { findByTestId } = Render(
<ThemeProvider theme={AppTheme}>
<BodyCardConfirmacaoSeguranca />
</ThemeProvider>,
);
const email = findByTestId('email');
const sms = findByTestId('sms');
fireEvent.change(email, { selectedCard: 'email' });
fireEvent.change(sms, { selectedCard: 'sms' });
});
I need to test the handleSelectCard function and its call on the components
below follows the code, it has not yet been tested. It is used inside the , can I be doing the test in the wrong place too? I'm lost
export default function CardEnvioCod({
tipoEnvio,
data,
handleSelectCard,
selectedCard,
}) {
const classes = useStyles();
const selected = selectedCard === tipoEnvio;
const icon =
tipoEnvio === TIPO_ENVIO.EMAIL ? (
<EmailOutlined
className={clsx(classes.icon, selected && classes.selected)}
/>
) : (
<PhoneAndroidOutlinedIcon
className={clsx(classes.icon, selected && classes.selected)}
/>
);
const text =
tipoEnvio === TIPO_ENVIO.EMAIL
? 'Enviar por e-mail:'
: 'Enviar por SMS:';
useEffect(() => {}, []);
return (
<Grid container justify="center" className={classes.container}>
<Grid
item
component={Paper}
variant="outlined"
lg={7}
xs={12}
square
elevation={0}
className={clsx(classes.root, selected && classes.selected)}
onClick={handleSelectCard}
>
<Grid container justify="space-between" alignItems="center">
<Grid item lg={1} xs={2}>
<Icon>{icon}</Icon>
</Grid>
<Grid item xs={8}>
<Grid container justify="flex-start" direction="column">
<Typography
className={clsx(
classes.titleCard,
selected && classes.selected,
)}
>
{text}
</Typography>
<Typography
title={data}
className={classes.divData}
>
{data}
</Typography>
</Grid>
</Grid>
<Grid item lg={1} xs={2}>
<Grid container justify="flex-end">
{selected ? (
<CheckBoxIcon
fontSize="large"
cursor="pointer"
/>
) : (
<CheckBoxOutlineBlank
color="primary"
fontSize="large"
cursor="pointer"
/>
)}
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
);
}

Resources