I'm using logic to check my checkboxes. The functionality allows you to check the parent checkbox and all the children are checked. You can also check or uncheck an individual checkbox. The problem is that I'm using pagination in my component, so if I check the first checkbox on page 1, the first checkbox on page 2 will also be checked. I believe this is because the markup occurs by index. How can I solve this problem?
import { useUsers } from '../../services/hooks/useUsers';
import {
Box,
Button,
Checkbox,
Flex,
Heading,
Icon,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
Text,
useBreakpointValue,
Spinner,
Link,
HStack,
useDisclosure,
} from '#chakra-ui/react';
import { Header } from '../../components/Header';
import { NotificationModal } from '../../components/NotificationModal';
import { Sidebar } from '../../components/Sidebar';
import { RiAddLine } from 'react-icons/ri';
import { CgImport } from 'react-icons/cg';
import { TbEdit } from 'react-icons/tb';
import { FaWhatsapp } from 'react-icons/fa';
import { CgNotes } from 'react-icons/cg';
import { Pagination } from '../../components/Pagination';
import NextLink from 'next/link';
import { useState } from 'react';
import { queryClient } from '../../services/queryClient';
import { api } from '../../services/api';
export default function UserList() {
const [page, setPage] = useState(1);
const { data, isLoading, isFetching, error } = useUsers(page);
const [checkedItems, setCheckedItems] = useState([false]);
const allChecked = checkedItems.every(Boolean);
const isIndeterminate = checkedItems.some(Boolean) && !allChecked;
async function handlePrefetchUser(userId: string) {
await queryClient.prefetchQuery(
['user', userId],
async () => {
const response = await api.get(`users/${userId}`);
return response.data;
},
{
staleTime: 1000 * 60 * 10,
}
);
}
return (
<Box>
<Header />
<Flex my="6" maxWidth={1480} mx="auto" w="94%">
<Sidebar />
<Box flex="1" borderRadius={8} bg="gray.800" p="8" overflow="hidden">
<Flex mb="8" justify="space-between" align="center">
<Heading size="lg" fontWeight="normal">
Alunos
{!isLoading && isFetching && (
<Spinner size="sm" color="gray.500" ml="4" />
)}
</Heading>
<HStack spacing={4}>
<NextLink href="/users/create" passHref>
<Button as="a" size="sm" fontSize="sm" colorScheme="orange">
<Icon as={RiAddLine} fontSize="20" />
</Button>
</NextLink>
<NotificationModal />
<Button
as="a"
size="sm"
fontSize="sm"
colorScheme="blue"
cursor="pointer"
>
<Icon as={CgImport} fontSize="20" />
</Button>
</HStack>
</Flex>
{isLoading ? (
<Flex justify="center" align="center">
<Spinner />
</Flex>
) : error ? (
<Flex justify="center">
<Text>Falha ao obter dados dos usuários.</Text>
</Flex>
) : (
<>
<Table colorScheme="whiteAlpha" overflow="none" size="md">
<Thead>
<Tr>
<Th px={['4', '4', '6']} color="gray.300">
<Checkbox
colorScheme="orange"
isChecked={allChecked}
isIndeterminate={isIndeterminate}
onChange={(e) =>
setCheckedItems(
data.users.map(() => e.target.checked)
)
}
/>
</Th>
<Th w="100%">Alunos</Th>
<Th>Ações</Th>
{/* <Th w="8"></Th> */}
</Tr>
</Thead>
<Tbody>
{data.users.map((user, index) => (
<Tr key={user.id}>
<Td px={['4', '4', '6']}>
<Checkbox
key={user.id}
isChecked={checkedItems[index]}
colorScheme="orange"
onChange={(e) =>
setCheckedItems([
...checkedItems.slice(0, index),
e.target.checked,
...checkedItems.slice(index + 1),
])
}
/>
</Td>
<Td>
<Box>
<Link
color="orange.400"
onMouseEnter={() => handlePrefetchUser(user.id)}
>
<Text fontWeight="bold" fontSize={['sm', 'md']}>
{user.name}
</Text>
</Link>
</Box>
</Td>
<Td>
<Box cursor="pointer">
<HStack spacing={6}>
<Icon
as={TbEdit}
color="gray.400"
fontSize="22"
_hover={{
color: 'white',
}}
/>
<Icon
as={FaWhatsapp}
color="gray.400"
fontSize="22"
_hover={{
color: 'white',
}}
/>
<Icon
as={CgNotes}
color="gray.400"
fontSize="22"
_hover={{
color: 'white',
}}
/>
</HStack>
</Box>
</Td>
</Tr>
))}
</Tbody>
</Table>
<Pagination
totalCountOfRegisters={data.totalCount}
currentPage={page}
onPageChange={setPage}
/>
</>
)}
</Box>
</Flex>
</Box>
);
}
Add a key attribute with a value equal to the page number for the component which wraps your checkboxes.
So when the page number changes the checkboxes component will re-initialize.
Related
I'm wanting to do a checkbox functionality, where you click on the parent checkbox and it checks all the children. But you can also select an individual checkbox. I'm trying to do this using Chakra UI, but in the documentation they do it using just two checkboxes. In my case, I'm using map to iterate, so the problem that occurs is that I can only check them all, but I can't uncheck an individual checkbox. How would I resolve this?
import NextLink from 'next/link';
import { useState } from 'react';
import { queryClient } from '../../services/queryClient';
import { api } from '../../services/api';
export default function UserList() {
const [page, setPage] = useState(1);
const [checkedItems, setCheckedItems] = useState([false, false]);
const allChecked = checkedItems.every(Boolean);
const isIndeterminate = checkedItems.some(Boolean) && !allChecked;
const { data, isLoading, isFetching, error } = useUsers(page);
async function handlePrefetchUser(userId: string) {
await queryClient.prefetchQuery(
['user', userId],
async () => {
const response = await api.get(`users/${userId}`);
return response.data;
},
{
staleTime: 1000 * 60 * 10,
}
);
}
return (
<Box>
<Header />
<Flex my="6" maxWidth={1480} mx="auto" px="6">
<Sidebar />
<Box flex="1" borderRadius={8} bg="gray.800" p="8">
<Flex mb="8" justify="space-between" align="center">
<Heading size="lg" fontWeight="normal">
Alunos
{!isLoading && isFetching && (
<Spinner size="sm" color="gray.500" ml="4" />
)}
</Heading>
<HStack spacing={4}>
<NextLink href="/users/create" passHref>
<Button as="a" size="sm" fontSize="sm" colorScheme="orange">
<Icon as={RiAddLine} fontSize="20" />
</Button>
</NextLink>
<NotificationModal />
<Button
as="a"
size="sm"
fontSize="sm"
colorScheme="green"
cursor="pointer"
>
<Icon as={CgImport} />
</Button>
</HStack>
</Flex>
{isLoading ? (
<Flex justify="center" align="center">
<Spinner />
</Flex>
) : error ? (
<Flex justify="center">
<Text>Falha ao obter dados dos usuários.</Text>
</Flex>
) : (
<>
<Table colorScheme="whiteAlpha">
<Thead>
<Tr>
<Th px={['4', '4', '6']} color="gray.300" w="8">
<Checkbox
colorScheme="orange"
isChecked={allChecked}
isIndeterminate={isIndeterminate}
onChange={(e) =>
setCheckedItems([e.target.checked, e.target.checked])
}
/>
</Th>
<Th>Usuários</Th>
<Th>Ações</Th>
<Th w="8"></Th>
</Tr>
</Thead>
<Tbody>
{data.users.map((user) => (
<Tr key={user.id}>
<Td px={['4', '4', '6']}>
<Checkbox
colorScheme="orange"
isChecked={checkedItems[0]}
onChange={(e) =>
setCheckedItems([e.target.checked, checkedItems[1]])
}
/>
</Td>
<Td>
<Box>
<Link
color="orange.400"
onMouseEnter={() => handlePrefetchUser(user.id)}
>
<Text fontWeight="bold">{user.name}</Text>
</Link>
<Text fontSize="sm" color="gray.300">
{user.email}
</Text>
</Box>
</Td>
<Td>
<Box cursor="pointer">
<HStack spacing={4}>
<Text
color="orange.400"
_hover={{
color: 'orange.500',
}}
>
Editar
</Text>
<Text
color="gray.300"
_hover={{
color: 'gray.500',
}}
>
Excluir
</Text>
</HStack>
</Box>
</Td>
</Tr>
))}
</Tbody>
</Table>
<Pagination
totalCountOfRegisters={data.totalCount}
currentPage={page}
onPageChange={setPage}
/>
</>
)}
</Box>
</Flex>
</Box>
);
}
Here is a simplified example of how to manage an unknown number of child checkboxes:
https://codesandbox.io/s/stupefied-currying-s3r7dr?file=/src/App.js
For your use case, it might be helpful to also move the checkboxes and checkbox state into a child component that only renders once the users are loaded. That would allow you to set the initial checkbox state with the correct number of users.
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;
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 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}