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;
Related
My plan is to build a step-by-step from where users can add more sections as needed to a form. This plan is divided into several files to achieve the result, including a parent, a child, and the Quill editor component. I've followed the examples found in the ANT Design website: https://ant.design/components/form/#components-form-demo-dynamic-form-items but failed to replicate the example.
This is the setup for the React-Quill Editor component:
//texteditorsimple.js
import React from 'react';
import ReactQuill, {Quill} from 'react-quill';
import MagicUrl from 'quill-magic-url';
import 'react-quill/dist/quill.snow.css';
//registering magic url module
Quill.register('modules/magicUrl', MagicUrl);
const modules = {
toolbar: [
...
clipboard: {
...
},
magicUrl: true,
};
const formats = [
'list',
'bullet',
'link',
];
const TextEditorSimple = ({ value, onChange, placeholder }) => {
return (
<>
<ReactQuill
theme="snow"
value={value || ''}
modules={modules}
formats={formats}
onChange={onChange}
placeholder={placeholder} >
</ReactQuill>
<div id='counter'></div>
</>
)
}
export default TextEditorSimple;
This is the Parent component:
//parent.js
import React, { useState, useEffect } from 'react';
...
import Child from '../child';
const Parent = () => {
const [ formValue2, setFormValue2 ] = useState([]);
const deleteY = (data) => {
const newArrayFormValue = formValue2.filter(obj => !obj.name.includes(data));
setFormValue2(newArrayFormValue);
}
return (
<Row justify='center' align="top" style={{ padding: 10}}>
<Col>
<Child deleteX={deleteY} fields={formValue2} onChange={(newFields) => {setFormValue2(newFields)}}/>
</Col>
</Row>
)
}
export default Parent;
And this is the Child component:
//child.js
import React from 'react';
...
import TextEditorSimple from './texteditorsimple';
const Child = ({fields, onChange, deleteX}) => {
<Row justify='start' align="top">
<Col xs={24} style={{ padding: 10}}>
<h2>Pengenalan & Langkah-langkah</h2>
<Form
fields={fields}
name="dynamic_form_nest_item"
onFinish={onFinish}
autoComplete="off"
onFieldsChange={(_,allFields) =>{
onChange(allFields);
}} >
<Form.List name="tutorial" style={{ width: '100%'}}>
{(fields, { add, remove }) => (
<>
{fields.map(({ index, key, name, fieldKey, ...restField }) => (
<div key={key}>
<Row>
{
name === 0 ?
<Col xs={24} style={{ padding: 5 }}>
<Form.Item
{...restField}
label='Pengenalan'
name={[name, 'pengenalan']}
fieldKey={[fieldKey, 'pengenalan']}
>
<TextEditorSimple/>
</Form.Item>
</Col>
:
<Col xs={24} md={16} style={{ padding: 5 }}>
<Form.Item
{...restField}
label={'Langkah '+ name}
name={[name, 'langkah'+name]}
fieldKey={[fieldKey, 'langkah'+name]}
>
<TextEditorSimple/>
</Form.Item>
</Col>
}
<Col xs={24} md={8} style={{ padding: 5 }}>
{
name === 0 ?
''
:
<Space align="start">
<Form.Item
{...restField}
name={[name, 'image'+name]}
fieldKey={[fieldKey, 'image'+name]}
valuePropName="fileList"
getValueFromEvent={normFile}
extra="Muat naik satu (1) imej/tangkap layar">
<Upload
name="image"
customRequest={dummyRequest}
listType="picture-card"
maxCount={1} >
{/* <Button icon={<UploadOutlined />}>Muat naik imej/ tangkap layar</Button> */}
{uploadButton}
</Upload>
</Form.Item>
<DeleteOutlined onClick={() => { remove(name); }} style={{ color: 'red'}}/>
</Space>
}
</Col>
</Row>
<Divider/>
</div>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Tambah Langkah
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Col>
</Row>
}
export default Child;
I'm trying to catch the changes made on the Form.List 'tutorial' (add, delete) in the Child and uplift the value to the Parent and store them in the state. But the deletion somehow not reflected in the UI, as the element that should have been deleted persisted on page. Tried to manually change the state through poorly coded deleteX and deleteY function, but not successful.
The GIF showing the error can be viewed here: http://www.giphy.com/gifs/HsIjHuckIflqOr3gOO
I'm writing code that empties box from text and replaces that text with new text when link is clicked. But for now it's only adding text below the old text.
This must be done only with JavaScript if possible.
I have tried to find information from everywhere I know and haven't found anything that helps.
Here's my code so far:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import LocalStorageService from '../../../AvainiaTools/LocalStorageService.js';
import AvainiaCore from 'avainia-core-api';
//Components
import ApartmentReview from '../../partials/ApartmentReview/ApartmentReview.js';
//Chakras
import { Box, Divider, Text, Flex, Link } from '#chakra-ui/layout';
import { Image } from '#chakra-ui/image';
class CompanyPage extends Component {
constructor(props) {
super(props);
this.state = {
tab: 'default',
environment: 'environment',
security: 'security',
helps: 'helps',
faqs: 'faqs'
};
}
componentDidMount = () => {
}
render() {
const params = `${this.props.match.params.id}/${this.props.match.params.projectId}`;
const environment = this.state.environment;
const security = this.state.security;
const helps = this.state.helps;
const faqs = this.state.faqs;
return (
<Box bg="white" mb="4">
<Box>
<Box p={[4, 8, 4, 8]}>
<Text fontWeight="bold" fontSize="16">Testdata</Text>
<Divider display={{ base: "none", md: "block"}} width="100%" />
<Box bg="lightgray" p={[4, 8, 4, 8]} ml="800" mt="-4" mb="10">
<Flex mb="4">
<Link
onClick={() => this.setState({ environment: !environment})}
color="black" fontSize="14">Environment</Link>
</Flex>
<Flex mb="4">
<Link
onClick={() => this.setState({ security: !security})}
color="black" fontSize="14">Safety at work</Link>
</Flex>
<Flex mb="4">
<Link
onClick={() => this.setState({ helps: !helps})}
color="black" fontSize="14">Useful Links</Link>
</Flex>
<Link
onClick={() => this.setState({ faqs: !faqs})}
color="black" fontSize="14">FAQ</Link>
</Box>
<Box marginLeft="3">
{ this.state.tab == 'default' &&
<Box bg="white" marginTop="-230" marginRight="445">
<Text fontSize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontSize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontWeight="bold" fontSize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
<br></br>
<Text fontsize="14" margin-left="4">Testdata</Text>
</Box>
}
{this.state.environment !== 'environment' &&
<Box marginRight="445">
<Box bg="white" w="100%">
<Text fontSize="14">Testidata</Text>
</Box>
</Box>
}
{this.state.security !== 'security' &&
<Box marginRight="445">
<Box bg="white" w="100%">
<Text fontSize="14">Testdata</Text>
</Box>
</Box>
}
{this.state.helps !== 'helps' &&
<Box marginRight="445">
<Box bg="white" w="100%">
<Text fontSize="14">Testdata</Text>
</Box>
</Box>
}
{this.state.faqs !== 'faqs' &&
<Box marginRight="445">
<Box bg="white" w="100%">
<Text fontSize="14">Testdata</Text>
</Box>
</Box>
}
</Box>
</Box>
</Box>
<Box p={[4, 8, 4, 8]}>
<Image src="https://via.placeholder.com/1050x195" alt="placeholder" />
</Box>
</Box>
)
}
}
export default withRouter(CompanyPage);
I succeeded to edit the codes of links' onClick so that sub-pages' text replaces original when link is clicked. There's my new link codes:
<Box bg="lightgray" p={[4, 8, 4, 8]} ml="800" mt="-4" mb="10">
<Flex mb="4">
<Link
onClick={() => this.setState({ environment: !environment, tab: !environment})}
color="black" fontSize="14">Environment</Link>
</Flex>
<Flex mb="4">
<Link
onClick={() => this.setState({ security: !security, tab: !security, })}
color="black" fontSize="14">Safety at work</Link>
</Flex>
<Flex mb="4">
<Link
onClick={() => this.setState({ helps: !helps, tab: !helps})}
color="black" fontSize="14">Useful links</Link>
</Flex>
<Link
onClick={() => this.setState({ faqs: !faqs, tab: !faqs})}
color="black" fontSize="14">FAQs</Link>
</Box>
But when I click link in the sub-page, it adds new text below old one. It should replace old sub-page's next with new sub-page's text. How should I edit my code?
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>
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}
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.