Filtering When a Button is clicked - javascript

I currently have a search input which filters through the github repos. I then have a drop down select which allows a user to filter based on the language of the code used. I instead would like to use buttons rather than a drop down. Is there a way to filter the results on click as opposed to onChange like I am doing with the drop down. My code is as follows:
const Profile = () => {
const [formData, setFormData] = useState(INITIAL_STATE)
const [updated, setUpdated] = useState(false)
const [created, setCreated] = useState(false)
const { data } = useContext(GithubContext)
const handleUpdated = () => {
setUpdated(!updated)
data &&
data.sort((a, b) => {
if (updated) return a.updated_at > b.updated_at ? -1 : 1
return a.updated_at > b.updated_at ? 1 : -1
})
}
const handleCreated = () => {
setCreated(!created)
data &&
data.sort((a, b) => {
if (created) return a.created_at > b.created_at ? -1 : 1
return a.created_at > b.created_at ? 1 : -1
})
}
const handleInputChange = field => e => {
setFormData({ ...formData, [field]: e.target.value })
}
const classes = useStyles()
return (
<>
<div style={{ marginTop: 85, marginBottom: 85 }}>
<Container className={classes.dashboardContainer}>
<Card className={classes.card} style={{ width: '100%' }}>
<CardContent className={classes.content}>
<div className={classes.form}>
<Grid
container
spacing={2}
alignItems='center'
justify='space-between'
>
<Grid item sm={4} xs={12} className={classes.grid}>
<SelectStatus
language={formData.language}
handleInputChange={handleInputChange}
/>
</Grid>
<Grid item sm={4} xs={12} className={classes.grid}>
<TextField
className={classes.jobField}
margin='normal'
fullWidth
id='search'
name='search'
label='Search by Title'
placeholder='Search by Title'
onChange={handleInputChange('search')}
value={formData.search}
/>
</Grid>
<Grid item sm={2} xs={12} className={classes.grid}>
<Button
variant='contained'
color='primary'
onClick={handleUpdated}
>
Updated {updated ? '(oldest)' : '(newest)'}
</Button>
</Grid>
<Grid item sm={2} xs={12} className={classes.grid}>
<Button
fullWidth
variant='contained'
color='primary'
onClick={handleCreated}
>
Created {created ? '(oldest)' : '(newest)'}
</Button>
</Grid>
</Grid>
</div>
</CardContent>
</Card>
<div
style={{
textAlign: 'center'
}}
>
<Button variant='outlined' color='primary'>
JavaScript
</Button>
<Button variant='outlined' color='primary'>
Primary
</Button>
<Button variant='outlined' color='primary'>
Secondary
</Button>
<Button variant='outlined' color='primary'>
Disabled
</Button>
<Button variant='outlined' color='primary'>
Link
</Button>
<br />
</div>
</Container>
{!data ? (
<h1 className={classes.loading}>Initializing Repos...</h1>
) : (
<Container style={{ padding: 10 }}>
{!data ? (
<div style={{ placeItems: 'center' }}>Loading...</div>
) : (
<Grid container alignItems='center' spacing={4}>
{data &&
data
.filter(data => {
if (formData.language === 'All') return true
return data.language === formData.language
})
.filter(data => {
if (formData.search === '') return true
return (data.name + data.language)
.toLowerCase()
.includes(formData.search.toLowerCase())
})
.map(user => <RepoCard key={user.id} user={user} />)}
</Grid>
)}
</Container>
)}
</div>
</>
)
}
export default Profile
and here is the drop down component:
import React, { useRef } from "react"
// MUI stuff
import Select from "#material-ui/core/Select"
import InputLabel from "#material-ui/core/InputLabel"
import MenuItem from "#material-ui/core/MenuItem"
import FormControl from "#material-ui/core/FormControl"
const SelectStatus = ({ language, handleInputChange }) => {
const inputLabel = useRef(null)
return (
<FormControl style={{ width: "100%" }}>
<InputLabel ref={inputLabel} id='demo-simple-select-outlined-label'>
Status
</InputLabel>
<Select
labelId='demo-simple-select-outlined-label'
id='demo-simple-select-outlined'
value={language}
onChange={handleInputChange("language")}
fullWidth
>
<MenuItem value='All'>All</MenuItem>
<MenuItem value='HTML'>HTML</MenuItem>
<MenuItem value='JavaScript'>JavaScript</MenuItem>
<MenuItem value='Ruby'>Ruby</MenuItem>
</Select>
</FormControl>
)
}
export default SelectStatus
Any suggestions would be extremely helpful!!!

You can just use buttons and add them a value so it will work exactly the same as the dropdown
const handleInputChange = field => e => {
setFormData({ ...formData, [field]: e.target.value })
}
<Button onClick={handleInputChange('language')} value="All">All</Button>
<Button onClick={handleInputChange('language')} value="HTML")>HTML</Button>
<Button onClick={handleInputChange('Javascript')} value="Javascript">JavaScript</Button>
<Button onClick={handleInputChange('Ruby')} value="Ruby">Ruby</Button>

Related

Add a text field in MUI Autocomplete options dropdown

I want to add an editable text field in dropdown options of MUI Autocomplete.
I was able to add MUI text field by adding it in renderOption prop of Autocomplete.
Problem
I am not able to edit the text fields since the autocomplete selection overrides it.
<Autocomplete
open={open}
multiple
value={!!value ? [value] : []}
classes={{ popupIndicatorOpen: classes.popupOpen }}
closeIcon={null}
popupIcon={<KeyboardArrowDownIcon style={{ opacity: 0.6 }}/>}
forcePopupIcon
style={{ width: 300 }}
options={options} //array
getOptionLabel={opt => isInstanceOfIRange(opt) ? Concatenate([opt.min.value, opt.max.value], ' - ') : opt.value.toString() || ''}
renderTags={(opts, getTagProps) =>
opts.map((opt, index) => (
<CustomTag key={index.toString()} opt={opt} {...getTagProps({ index })} classes={classes} />)
)}
onChange={handleChange}/
renderInput={params => (
<TextField
{...params}
label={props.label}
variant='filled'
onFocus={() => setOpen(true)}
onInput={onInputChange}
/>
)}
renderOption={(option, { selected }) => (
<CustomOption option={option} selected={selected} />
)}
PopperComponent={PopperComponent}
PaperComponent={PaperComponent}
/>
Custom Options rendering text box
const CustomOption = ({ selected, option }) => (
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Checkbox
size="small"
color="primary"
checked={selected}
style={{ marginRight: 8 }}
icon={<RadioButtonUncheckedIcon />}
checkedIcon={<RadioButtonCheckedIcon />}
/>
{
isInstanceOfIRange(option) ? <RangeRender option={option} /> : <SingleRender
option={option} />
}
</div>
);
The Two text boxes are rendered using this component
const RangeRender = ({ option: { min, max } }) => {
const { labelPostfix, labelPrefix } = useContext(DropDownContext);
const inputProps = labelPrefix ? {
startAdornment: <InputAdornment position="start">{labelPrefix}</InputAdornment>,
} : labelPostfix ? { endAdornment: <InputAdornment position="end">{labelPostfix}
</InputAdornment>,} : {};
return (
<Box style={{ display: 'flex', gap: 12 }}>
<NummberField
type="number"
variant="outlined"
value={min.value}
InputProps={inputProps}
/>
<NummberField
type="number"
variant="outlined"
value={max.value}
InputProps={inputProps}
/>
</Box>);
};

How to hide a Material UI grid item?

I am trying to hide the grid item of a certain product and let the others slide in. Right now i am using the display:none property but it hides the item instantaneous. I already have the products filtered and i am checking i want to hide the products that are not filtered using somekind an animation. model:
import React, { useState, useEffect } from "react";
import { Container, Grid, Card, Skeleton, useMediaQuery, Grow, } from "#mui/material";
import Filter from "./Filter";
import Product from "./Product/Product";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { Box } from "#mui/system";
const Products = () => {
const { category } = useParams();
const [sortOption, setSortOption] = useState("name");
const [newProducts, setNewProducts] = useState([]);
const [menu, setMenu] = useState(false);
useEffect(() => {
const selectedSort = sessionStorage.getItem("sortOption") || "name";
setSortOption(selectedSort);
}, []);
const sort = (items, option) => {
switch (option) {
case "name":
return items.sort((a, b) => (a.name > b.name ? 1 : -1));
case "up":
return items.sort((a, b) => (a.price.raw > b.price.raw ? 1 : -1));
case "down":
return items.sort((a, b) => (a.price.raw < b.price.raw ? 1 : -1));
default:
return items.sort((a, b) => (a.name > b.name ? 1 : -1));
}
};
const mobile = useMediaQuery("(max-width:600px)");
const { products, loading } = useSelector((state) => state.products);
let items = products.filter((product) => product.categories[0].slug === category);
let newItems = [];
newProducts.forEach((product) => {
items.forEach((item) => {
if (item.id === product.id) newItems.push(item);
});
});
if (newProducts.length > 0) newItems = sort(newItems, sortOption);
else newItems = sort(items, sortOption);
const renderProducts = () => (
<>
<Container maxWidth="lg" >
<Grid
container
direction="row"
spacing={3}
sx={{
pl: !mobile && menu && "300px",
transition: ".3s",
}}>
{items.map((product) => (
newItems.some(newItem => newItem.id === product.id) ? (
<Grid item xs={12} sm={6} md={4} lg={3} key={product.id} >
<Product product={product} disable={newItems.some(newItem => newItem.id === product.id)} />
</Grid>
) : null
))}
</Grid>
</Container>
</>
);
const loadingView = () => (
<Container maxWidth="lg" >
<Grid
container
direction="row"
spacing={3}
sx={{
pl: !mobile && menu && "300px",
transition: ".3s",
}}>
<Grid item xs={12} sm={6} md={4} lg={3}>
<Skeleton variant="rectangular" width="100%" sx={{ height: ["60vh", "50vh"], mb: 1 }} />
<Box sx={{ p: 2 }}>
<Skeleton variant="text" width="100%" sx={{ height: "20px", mb: 1 }} />
<Card variant="flex" flex="flex">
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", mr: 1 }} />
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", }} />
</Card>
<Skeleton variant="text" width="50%" sx={{ height: "20px" }} />
</Box>
<Skeleton variant="rectangular" width="100%" sx={{ height: "35px" }} />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<Skeleton variant="rectangular" width="100%" sx={{ height: ["60vh", "50vh"], mb: 1 }} />
<Box sx={{ p: 2 }}>
<Skeleton variant="text" width="100%" sx={{ height: "20px", mb: 1 }} />
<Card variant="flex" flex="flex">
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", mr: 1 }} />
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", }} />
</Card>
<Skeleton variant="text" width="50%" sx={{ height: "20px" }} />
</Box>
<Skeleton variant="rectangular" width="100%" sx={{ height: "35px" }} />
</Grid>
<Grid item xs={12} sm={6} md={4} lg={3}>
<Skeleton variant="rectangular" width="100%" sx={{ height: ["60vh", "50vh"], mb: 1 }} />
<Box sx={{ p: 2 }}>
<Skeleton variant="text" width="100%" sx={{ height: "20px", mb: 1 }} />
<Card variant="flex" flex="flex">
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", mr: 1 }} />
<Skeleton variant="rectangular" width="30px" sx={{ height: "10px", }} />
</Card>
<Skeleton variant="text" width="50%" sx={{ height: "20px" }} />
</Box>
<Skeleton variant="rectangular" width="100%" sx={{ height: "35px" }} />
</Grid>
</Grid>
</Container>
);
return <>
<Filter
sortOption={sortOption}
setSortOption={setSortOption}
menu={menu}
setMenu={setMenu}
products={items}
setNewProducts={setNewProducts}
category={category}
/>
{!loading ? renderProducts() : loadingView()}</>;
};
export default Products;
You need to filter the items as you expect by color or size first.
...
const filteredProducts = newItems.filter(newItem => newItem.id === product.id);
...
<Container maxWidth="lg" >
<Grid
container
// You don't need to use direction="row" since it is default props value
spacing={3}
>
{filteredProducts.map((product) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={product.id}>
<Product product={product} disable={newItems.some(newItem => newItem.id === product.id)} />
</Grid>
))}
</Grid>
</Container>
...
If you want to filter the products more accurately, then you could build a customer filter function. Please refer to this.

Formik Field Array field's id change when wrap with modal

I'm using Formik with chakra UI to create dynamic forms using the Fomrik Field array. and I Open my form in Chakra modal, problem is when I open my form in modal it change field name and id to array last object id , When i remove modal its works as expected, I Recreated my problem here
my code sample
<Box>
{/* start card grid */}
<Box py="12" px={{ base: "6", md: "0" }}>
<FormikProvider value={formik}>
<Box as="section" maxW={{ base: "xs", md: "full" }} mx="auto">
<form onSubmit={formik.handleSubmit}>
<SimpleGrid
columns={{ base: 1, sm: 1, md: 3, xl: 4 }}
spacing="6"
>
<FieldArray name="locations">
{() =>
formik.values?.locations?.map(
(props: any, index: string) => {
const { location, reviews } = props;
return (
<>
<Modal
isOpen={isOpen}
onClose={onClose}
>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody >
<>
<FormLabel>Page URL</FormLabel>
<InputGroup size="md">
<InputLeftAddon
borderTopEndRadius={0}
borderBottomEndRadius={0}
borderColor="grayExtend.300"
bg="grayExtend.100"
fontSize="0.8rem"
px={2}
>
{modal.urlPrefix}
</InputLeftAddon>
<>
<Input
name={`locations.${index}.facebookUrl`}
value={
formik.values.locations[
index
].facebookUrl
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="facebook name"
type="text"
borderWidth="1.5px"
fontSize="0.8rem"
borderColor="grayExtend.300"
borderTopStartRadius={0}
borderBottomStartRadius={
0
}
/>
<Box
fontSize="0.875rem"
color="red"
mt={2}
>
</Box>
</>
</InputGroup>
</>
</ModalBody>
<ModalFooter mb={4}>
<Button
key={`btn${
Math.random() * 1000
}`}
id={modal.web}
colorScheme="blue"
border="1px"
isFullWidth
type="submit"
_hover={{ bg: "#152aa3" }}
disabled={formik.isSubmitting}
>
Submit
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Flex
direction="column"
alignItems="center"
rounded="md"
padding="8"
position="relative"
shadow="base"
id={index}
>
<Box
position="absolute"
inset="0"
height="20"
roundedTop="inherit"
/>
<VStack
spacing="4"
mt={3}
>
{buttons.map(
({ title, id, icon }) => (
<Button
key={id+1}
id={id}
colorScheme="grayExtend.100"
borderWidth="1px"
borderColor="grayExtend.300"
variant="outline"
p="10px"
borderRadius="4px"
minH="60px"
minW="300px"
onClick={(e) =>
handleOpenModal(e)
}
className={
formik.values.locations[index]
.facebookUrl &&
formik.errors &&
formik.errors.locations &&
getIn(
formik.errors,
`locations.${index}.facebookUrl`
) &&
"success"
}
display="flex"
justifyContent="start"
bg="#f5faff"
fontSize="0.875rem"
>
{title}
</Button>
)
)}
</VStack>
</Flex>
</>
);
}
)
}
</FieldArray>
</SimpleGrid>
</form>
</Box>
</FormikProvider>
</Box>
{/* end card grid */}
</Box>
as I can see you create modal views for each field. This is not a bad solution but it is better to make one modal and give it data to display (I take out the modal component from the map cycle)
Now the main problem to set special data for each button. I add the state new field id.
In formik inputs change values and names to locations[${modal.id}].facebookUrl
The reason, why are you getting last modal is not controlling it's statement. There is now field which says React not to show 2nd,3nd or other components, so there are 4 modals on each other. You can also use your code but add condition which will hide or show your modal view for example
modal.id === idx && <Modal/>
Also link of this code
import {
Box,
useDisclosure,
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
SimpleGrid,
Flex,
VStack,
FormLabel,
Input,
InputGroup,
InputLeftAddon,
ModalFooter
} from "#chakra-ui/react";
import { FieldArray, getIn, useFormik, FormikProvider } from "formik";
import * as React from "react";
const Form = () => {
const [modal, setModal] = React.useState({
web: "",
urlPrefix: "",
id: 0
});
const { isOpen, onOpen, onClose } = useDisclosure();
const cardData: any = {
locations: [
{
facebookUrl: "fb 1"
},
{
facebookUrl: "fb2"
},
{
facebookUrl: ""
},
{
facebookUrl: ""
}
]
};
const buttons = [
{
id: "facebook",
title: "Facebook",
icon: ""
}
];
// modal func
const handleOpenModal = (e: any, idx: number) => {
const { id } = e.target;
if (id === "facebook") {
setModal({
...modal,
web: "facebook",
urlPrefix: "facebook.com",
id: idx
});
} else return null;
onOpen();
return null;
};
const formik = useFormik({
initialValues: cardData,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<Box>
{/* start card grid */}
<Box py="12" px={{ base: "6", md: "0" }}>
<FormikProvider value={formik}>
<Box as="section" maxW={{ base: "xs", md: "full" }} mx="auto">
<form onSubmit={formik.handleSubmit}>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody>
<>
<FormLabel>Page URL</FormLabel>
<InputGroup size="md">
<InputLeftAddon
borderTopEndRadius={0}
borderBottomEndRadius={0}
borderColor="grayExtend.300"
bg="grayExtend.100"
fontSize="0.8rem"
px={2}
>
{modal.urlPrefix}
</InputLeftAddon>
<>
<Input
name={`locations[${modal.id}].facebookUrl`}
value={
formik.values.locations[modal.id].facebookUrl
}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
placeholder="facebook name"
type="text"
borderWidth="1.5px"
fontSize="0.8rem"
borderColor="grayExtend.300"
borderTopStartRadius={0}
borderBottomStartRadius={0}
/>
<Box fontSize="0.875rem" color="red" mt={2}></Box>
</>
</InputGroup>
</>
</ModalBody>
<ModalFooter mb={4}>
<Button
key={`btn${Math.random() * 1000}`}
id={modal.web}
colorScheme="blue"
border="1px"
isFullWidth
type="submit"
_hover={{ bg: "#152aa3" }}
disabled={formik.isSubmitting}
>
Submit
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<SimpleGrid
columns={{ base: 1, sm: 1, md: 3, xl: 4 }}
spacing="6"
>
<FieldArray name="locations">
{() =>
formik.values?.locations?.map(
(props: any, index: number) => {
const { facebookUrl } = props;
return (
<>
<Flex
direction="column"
alignItems="center"
rounded="md"
padding="8"
position="relative"
shadow="base"
id={index}
>
<Box
position="absolute"
inset="0"
height="20"
roundedTop="inherit"
/>
<VStack spacing="4" mt={3}>
{buttons.map(({ title, id, icon }) => (
<Button
key={id + 1}
id={id}
colorScheme="grayExtend.100"
borderWidth="1px"
borderColor="grayExtend.300"
variant="outline"
p="10px"
borderRadius="4px"
minH="60px"
minW="300px"
onClick={(e) => handleOpenModal(e, index)}
className={
formik.values.locations[index]
.facebookUrl &&
formik.errors &&
formik.errors.locations &&
getIn(
formik.errors,
`locations.${index}.facebookUrl`
) &&
"success"
}
display="flex"
justifyContent="start"
bg="#f5faff"
fontSize="0.875rem"
>
{title}
</Button>
))}
</VStack>
</Flex>
</>
);
}
)
}
</FieldArray>
</SimpleGrid>
</form>
</Box>
</FormikProvider>
</Box>
{/* end card grid */}
</Box>
);
};
export default Form;

How to scrollToView for a specific item in an array

I have a component that scrolls to view after a comment has been submitted.
const commentSubmit = (e: any, id: number) => {
......
divRef.current.scrollIntoView({ behavior: "smooth" });
};
It scrolls to the comment where <div ref={divRef]></div> is placed which is at the bottom of the most recent comment. The probelem is that if i make a new post, it will scroll into the wrong div element. So its just scrolling at the very bottom. How can i tell ref to scroll to the a specific element based on what post the comment belongs to ?
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
// ignore this divRef in comment list its not being called in the comment list.
<CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
<div ref={divRef}></div>
postItemContainer.tsx
import React, { Fragment, useState, useCallback, useRef } from "react";
import Avatar from "#material-ui/core/Avatar";
import Button from "#material-ui/core/Button";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Typography from "#material-ui/core/Typography";
import DeleteOutlineOutlinedIcon from "#material-ui/icons/DeleteOutlineOutlined";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
import moment from "moment";
import { toast, ToastContainer } from "react-toastify";
import OurLink from "../../../common/OurLink";
import CommentForm from "../comment/CommentForm";
import CommentList from "../commentList/CommentList";
import OurModal from "../../../common/OurModal";
import "react-toastify/dist/ReactToastify.css";
function PostItemContainer(props: any) {
const [openModal, setOpenModal] = useState(false);
const [openForm, setOpenForm] = useState(false);
const [comment_body, setCommentBody] = useState("");
const [gifUrl, setGifUrl] = useState("");
const divRef = React.useRef<any>();
const writeComment = () => {
// this is the same as this.setState({ openForm: !this.state.open })
setOpenForm(!openForm);
};
const commentChange = (comment) => {
setGifUrl("");
setCommentBody(comment);
};
const selectGif = (e) => {
setGifUrl(e.images.downsized_large.url);
setCommentBody("");
// you wont be able to add text comment with a gif, it will look weird :(
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const commentSubmit = (e: any, id: number) => {
e.preventDefault();
const formData = {
comment_body,
id,
gifUrl,
};
props.postComment(formData);
setCommentBody("");
setOpenForm(false);
console.log(divRef);
divRef.current.scrollIntoView({ behavior: "smooth" });
};
const { post, currentUser, getNotifications } = props;
console.log(divRef);
return (
<Fragment>
{getNotifications && <ToastContainer autoClose={1000} position={toast.POSITION.BOTTOM_RIGHT} />}
<Grid item={true} sm={12} md={12} style={{ margin: "20px 0px" }}>
<Paper style={{ padding: "20px" }}>
<Typography variant="h5" align="left">
<OurLink
style={{ fontSize: "16px" }}
to={{
pathname: `/post/${post.id}`,
state: { post },
}}
title={post.title}
/>
</Typography>
<Grid item={true} sm={12} md={12} style={{ padding: "30px 0px" }}>
<Typography align="left">{post.postContent.slice(0, 50)}</Typography>
</Grid>
<Avatar
style={{
display: "inline-block",
margin: "-10px -20px",
padding: "0px 30px 0px 20px",
}}
sizes="small"
src={post.author.gravatar}
/>
<Typography display="inline" variant="subtitle1" align="left">
<OurLink
to={{
pathname: `/profile/${post.author.username}`,
state: { post },
}}
title={post.author.username}
/>
</Typography>
<Typography align="right">Likes: {post.likeCounts}</Typography>
<Grid container={true} spacing={1} style={{ padding: "20px 0px" }}>
<Grid item={true} sm={10} lg={10} md={10} style={{ padding: "0px 0px" }}>
<Typography align="left">
{currentUser && currentUser.user && post.userId === currentUser.user.id ? (
<span style={{ cursor: "pointer" }} onClick={() => props.deletePost(post.id, post.userId)}>
<DeleteOutlineOutlinedIcon style={{ margin: "-5px 0px" }} color="primary" /> <span>Delete</span>
</span>
) : null}
</Typography>
</Grid>
<Grid item={true} sm={2} lg={2} style={{ padding: "0px 15px" }}>
<Typography align="right">
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<span onClick={handleClickOpen}>
<FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
</span>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
{post.likedByMe === true ? (
<span style={{ cursor: "pointer" }} onClick={() => props.dislikePost(post.id)}>
<FavoriteIcon style={{ color: "red" }} />
</span>
) : (
<span onClick={() => props.likePost(post.id)}>
<FavoriteBorderIcon style={{ color: "red", cursor: "pointer" }} />
</span>
)}
</Fragment>
)}
</Typography>
</Grid>
</Grid>
<Typography variant="h6" align="left">
{moment(post.createdAt).calendar()}
</Typography>
<Grid item={true} sm={12} lg={12} style={{ paddingTop: "40px" }}>
{Object.entries(currentUser).length === 0 ? (
<Fragment>
<Button onClick={handleClickOpen} variant="outlined" component="span" color="primary">
{"Write A Comment"}
</Button>
{openModal ? <OurModal open={openModal} handleClose={handleCloseModal} /> : null}
</Fragment>
) : (
<Fragment>
<Button onClick={writeComment} variant="outlined" component="span" color="primary">
{openForm ? "Close" : "Write A Comment"}
</Button>
</Fragment>
)}
{openForm ? (
<CommentForm
commentChange={(e: any) => commentChange(e.target.value)}
comment_body={comment_body}
onSubmit={(e) => commentSubmit(e, post.id)}
gifUrl={selectGif}
isGif={gifUrl}
/>
) : null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
<div ref={divRef}></div>
</Grid>
</Paper>
</Grid>
</Fragment>
);
}
export default PostItemContainer
CommentList.tsx(just in case) how the comments are bing mapped and iterated
import React, { Fragment, useState } from "react";
import Grid from "#material-ui/core/Grid";
import OurSecondaryButton from "../../../common/OurSecondaryButton";
import CommentListContainer from "../commentListContainer/commentListContainer";
function CommentList(props: any) {
const [showMore, setShowMore] = useState<Number>(2);
const [openModal, setOpenModal] = useState(false);
const [showLessFlag, setShowLessFlag] = useState<Boolean>(false);
const the_comments = props.comments.length;
const inc = showMore as any;
const min = Math.min(2, the_comments - inc);
const showComments = (e) => {
e.preventDefault();
if (inc + 2 && inc <= the_comments) {
setShowMore(inc + 2);
setShowLessFlag(true);
} else {
setShowMore(the_comments);
}
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const showLessComments = (e) => {
e.preventDefault();
setShowMore(2);
setShowLessFlag(false);
};
const isBold = (comment) => {
return comment.userId === props.userId ? 800 : 400;
};
// show comments by recent, and have the latest comment at the bottom, with the previous one just before it.
const filterComments = props.comments
.slice(0)
.sort((a, b) => {
const date1 = new Date(a.createdAt) as any;
const date2 = new Date(b.createdAt) as any;
return date2 - date1;
})
.slice(0, inc)
.reverse();
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div key={i}>
<CommentListContainer comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
return (
<Grid>
<Fragment>
<div style={{ margin: "30px 0px" }}>
{props.comments.length > 2 ? (
<Fragment>
{min !== -1 && min !== -2 ? (
<Fragment>
{min !== 0 ? (
<OurSecondaryButton onClick={(e) => showComments(e)} component="span" color="secondary">
View {min !== -1 && min !== -2 ? min : 0} More Comments
</OurSecondaryButton>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : null}
</div>
</Fragment>
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment */}
{filterComments.map((comment, i) => (
<div key={i}>
<CommentListContainer comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
))}
</Fragment>
)}
</Grid>
);
}
// prevents un-necesary re renders
export default React.memo(CommentList);
The question seems quite complicated, but i over complicated it.
What i had to do was add this
setTimeout(() => {
divRef.current.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" });
}, 1500);
in the commentSubmit method, this will scroll to the recent comment.
const commentSubmit = (e: any, id: number) => {
e.preventDefault();
const formData = {
comment_body,
id,
gifUrl,
};
props.postComment(formData);
setCommentBody("");
setOpenForm(false);
console.log(divRef);
// divRef.current.scrollIntoView({ behavior: "smooth" });
// my attempt to scroll to the lastest comment.
setTimeout(() => {
divRef.current.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" });
}, 1500);
}
And make some adjustments to the code, if there are no comments divRef will throw an error upon adding a comment, because it is looking for a comment but there aren't any comments, so we will do this if there are comments less than 1 or 2. In my case im using 2 cause im showing 2 comments initially and i have a filter that shows more comments, and show less.
{post.Comments.length === 0 ? <div ref={divRef}></div> : null}
reference scrollIntoView Scrolls just too far
Rest of the code
{post.Comments.length === 0 ? <div ref={divRef}></div> : null}
{post.Comments.length > 0 ? (
<Fragment>
<Typography style={{ padding: "10px 0px", margin: "20px 0px" }}>Commments</Typography>
<CommentList ref={divRef} user={currentUser} deleteComment={props.deleteComment} userId={post.userId} postId={post.id} comments={post.Comments} {...props} />
{/* if show more hide show more button and show show less comments button */}
</Fragment>
) : (
<Grid item={true} sm={12} lg={12} style={{ padding: "30px 0px" }}>
<Typography>No Commments Yet</Typography>
</Grid>
)}
{post.Comments.length < 2 ? <div ref={divRef}></div> : null}

Nested fieldarray in formik

I am using formik for the forms. In my form, I need to use the concept of FieldArray and it's a nested one. I could not fill the nested forms with respective values. Here is how I have done
const Itinerary = ({ itineraries, push, remove }) => {
return (
<>
<Heading>Itinerary</Heading>
{itineraries && itineraries.length === 0 && (
<span
style={{
color: theme.color.primary,
padding: "0 10px",
cursor: "pointer"
}}
onClick={() => push({})}
>
+
</span>
)}
{itineraries &&
itineraries.length > 0 &&
itineraries.map((day, index) => {
return (
<React.Fragment key={index}>
<Row key={index} style={{ alignItems: "center" }}>
<Text>
How many
<DaysField
component={TextField}
name={`itineraries.${index}.days`}
placeholder="days"
normalize={val => val && parseInt(val)}
/>
of itinerary?
</Text>
{itineraries && itineraries.length - 1 === index && (
<>
<Button>
<Button.Round onClick={() => push({})}>+</Button.Round>
</Button>
<Button>
<Button.Round onClick={() => remove(index)}>
-
</Button.Round>
</Button>
</>
)}
</Row>
<FieldArray
name={`itineraries.${index}.itinerary`}
render={({ push, remove }) => (
<>
<Heading>
Fill up itinerary
{itineraries[index].itinerary &&
itineraries[index].itinerary.length === 0 && (
<span
style={{
color: theme.color.primary,
padding: "0 10px",
cursor: "pointer"
}}
onClick={() => push({})}
>
+
</span>
)}
</Heading>
{itineraries[index].itinerary &&
itineraries[index].itinerary.length > 0 &&
itineraries[index].itinerary.map((i, idx) => {
console.log(
"itinerary index",
itineraries[index].itinerary[idx]
);
return (
<React.Fragment>
<Row
style={{
alignItems: "center",
alignSelf: "center"
}}
>
<Col xs={12} md={3}>
<Field
component={TextField}
placeholder="Day"
name={`${itineraries[index].itinerary[idx]}.day`}
/>
</Col>
<Col xs={12} md={3}>
<Field
component={TextField}
placeholder="Description"
name={`${itineraries[index].itinerary[idx]}.description`}
/>
</Col>
<Col xs={12} md={3}>
<Field
component={TextField}
placeholder="Overnight"
name={`${itineraries[index].itinerary[idx]}.overnight`}
/>
</Col>
<Col xs={12} md={2}>
<Field
component={TextField}
placeholder="Altitude"
name={`${itineraries[index].itinerary}.altitude`}
normalize={value => {
return value && parseFloat(value);
}}
/>
</Col>
<Col xs={12} md={1}>
{itineraries &&
itineraries.length - 1 === index && (
<>
<ActionBtn>
<Button>
<Button.Round
onClick={() => push({})}
>
+
</Button.Round>
</Button>
<Button>
<Button.Round
onClick={() => remove(index)}
color={theme.color.red}
>
-
</Button.Round>
</Button>
</ActionBtn>
</>
)}
</Col>
</Row>
</React.Fragment>
);
})}
</>
)}
/>
</React.Fragment>
);
})}
</>
);
};
The itineraries initialValues looks like this
itineraries: [
{
days: 1,
itinerary: [
{
day: "Day 1",
description: "description",
overnight: "overnight info",
altitude: 150.5
},
{
day: "Day 2",
description: "description 2",
overnight: "overnight info",
altitude: 150.5
}
]
}
]
Only Nested part is not working. Can anyone help me at this, please?
You problem is in how you pass the name for the component:
name={`${itineraries[index].itinerary[idx]}.overnight`}
This way, what you are doing is passing the value of itineraries[index].itinerary[idx] to a string.
What you should do is pass that as a string, not it's value:
name={`itineraries[${index}].itinerary[${idx}].overnight`}
This way, only the indexes get the value printed, which is correct.
Here is the difference
${itineraries[index].itinerary[idx].overnight will return [object Object].overnight because you are access the value of itineraries[index].itinerary[idx] and it's calling .toString().
But itineraries[${index}].itinerary[${idx}].overnight will return itineraries[0].itinerary[0].overnight which is a valid formik string and it can get it's value.

Categories