How to modify when using GraphQL and Apollo - reactjs

I have a project and it is an e-commerce project and through this project I have an interface that displays all the collections on the website, and I want to edit a collection,
And the collection only has one element, which is the name of the collection, and when you click on the edit icon, a dialog appears asking to edit the name of the specified collection.
My problem is that when I press the "Save" button, the value to be modified is not modified and no action occurs.
And this is a picture of the shape of the request from backend:
This file expresses the dialog of the modification:
import { React, useState } from "react";
import {
Input,
Modal,
ModalBody,
ModalFooter,
ModalHeader,
Button,
} from "reactstrap";
import { gql, useMutation } from "#apollo/client";
import { GET_PRODUCTS } from "./dashboard";
const UPDATE_COLLECTION = gql`
mutation updateCollection($name: String!) {
updateCollection(updateCollection: { name: $name }) {
name
}
}
`;
const EditCollection = (id, { isPublic = false }) => {
let input;
const [modal, setModal] = useState(false);
const togglePopup = () => setModal(!modal);
const [open, setOpen] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [collectionInput, setCollectionInput] = useState("");
const updateCache = (cache, { data }) => {
// If this is for the public feed, do nothing
if (isPublic) {
return null;
}
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_PRODUCTS,
});
// Add the new todo to the cache
const newCollection = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_PRODUCTS,
data: { todos: [newCollection, ...existingTodos.todos] },
});
};
const resetInput = () => {
setCollectionInput("");
};
const [updateCollection] = useMutation(UPDATE_COLLECTION, {
update: updateCache,
onCompleted: resetInput,
});
return (
<div>
<Button onClick={togglePopup} style={{ backgroundColor: "none" }}>
<i
className="fa fa-pencil-square-o mr-1"
onclick={togglePopup}
aria-hidden="true"
></i>
</Button>
<Modal
isOpen={modal}
toggle={togglePopup}
centered
style={{ padding: "1rem" }}
>
<ModalHeader toggle={togglePopup} style={{ padding: "1rem" }}>
<h3>Edit Collection</h3>
</ModalHeader>
<ModalBody style={{ padding: "2rem" }}>
<Input
// value={name}
// onChange={(e) => setName(e.target.value)}
// style={{ padding}}
id="collectionName"
name="name"
placeholder="update collection name"
type="text"
/>
</ModalBody>
<ModalFooter style={{ paddingRight: "2rem" }}>
<Button
color="primary"
onclick={togglePopup}
onSubmit={(e) => {
e.preventDefault();
console.log("I am inside th function: ", e.target.value);
updateCollection({ variables: { name, id } });
message(e);
}}
>
Save
</Button>{" "}
<Button onclick={togglePopup}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default EditCollection;
And through this file, I called the file for the modification
dashboard.js:
const AllCollections = ({ id, name, img }) => {
return (
<tr>
<th scope="row">
<Media src={img} className="blur-up lazyloaded" />
</th>
<td>{id}</td>
<td>{name}</td>
<td>
<EditCollection id={id} />
<i className="fa fa-trash-o ml-1" aria-hidden="true"></i>
</td>
</tr>
);
};

You are defining only a name parameter, and then giving name and id. Change your GraphQl query to supply the id as well.
const UPDATE_COLLECTION = gql`
mutation updateCollection($name: String!, $id: ID!) {
updateCollection(updateCollection: { name: $name, id:$id }) {
name
}
}
`;
Also, to see whether the update has happened or get an error, add some console logs. And for that get them while calling useMutation:
const [updateCollection, { data, loading, error }] = useMutation(UPDATE_COLLECTION, {
update: updateCache,
onCompleted: resetInput,
});
console.log(data, loading, error);
An finally change the save button with the code below, as you are listening to onSubmit, which only works on form element.
<Button
color="primary"
onClick={(e) => {
e.preventDefault();
console.log("I am inside th function: ", e.target.value);
updateCollection({ variables: { name, id } });
message(e);
togglePopup();
}}
>
Save
</Button>

Related

useEffect doesn't re-render on state change or infinite looping issue

I have a component which contains a form and a list. When user adds an item to the list though the form, the item should display immediately in the list. I try to use useEffect to fetch data, useEffect without dependency causes an infinite request loop. I added empty array as dependency to prevent looping but in this case new item which is added doesn't display in the list until refreshing the page. How can I solve this issue? (I use antd and antd-form-builder to create the component)
here is my code:
function FieldSetting() {
const [form] = Form.useForm()
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [])
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta} />
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting
Just add a state that will refretch (trigger useEffect) after you have submitted the form. Be aware that it will refetch all the data from the API. This might bring scalability issues when the data grows.
function FieldSetting() {
const [form] = Form.useForm()
const [refetch, setRefetch] = useState(false) // <----- add this state
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
setRefetch(!refetch) // <----- set the refetch to change the state
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [refetch]) // <----- add the refetch here to trigger the effect
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta}
/>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting```
Whenever you manipulate your array just add a dummy state and change it
add this state
const [extra, setExtra] = useState(0)
when you change the state of your array like add or remove just add this line below
setExtra(extra+1)
what happens is that adding or removing data in an array don't count as a state change in react as per my understanding it need to be something different like true to false or in this case 0 to 1

Prisma, graphql and react hook forms: how to use created values in update form mutation

I am trying to make a form that has to create and update functionality. At the moment, when I get to the update form, I can enter new values and they do save to psql, but the form loads with blank fields instead of the values used to create the existing object. I can't find the settings I need to apply to use the existing object values in the update form so that it loads with the fields displaying the currently saved values.
My form has:
import * as React from "react"
import { CgTrash, CgEditMarkup } from "react-icons/cg"
import { VscIssues } from "react-icons/vsc"
import { gql } from "#apollo/client"
import {
Box,
Button,
ButtonGroup,
IconButton,
useDisclosure,
Wrap,
Stack,
Table,
Tbody,
Tr,
Td,
TableContainer,
Text
} from "#chakra-ui/react"
import Head from "next/head"
import { useToast } from "lib/hooks/useToast"
import { useForm } from "lib/hooks/useForm"
import { Form } from "components/Form"
import { FormError } from "components/FormError"
import { Input } from "components/Input"
import { Textarea } from "components/Textarea"
import {
// IssueGroupInput,
useAllIssueGroupsQuery,
useCreateIssueGroupMutation,
useUpdateIssueGroupMutation,
useDeleteIssueGroupMutation,
IssueGroup as IssueGroupGQLType,
} from "lib/graphql"
//
// import { AdminCreateIssueGroupForm } from "components/AdminCreateIssueGroupForm"
// import { AdminUpdateIssueGroupForm } from "components/AdminUpdateIssueGroupForm"
import { AdminLayout } from "components/AdminLayout"
import { AuthedHomeLayout } from "components/AuthedHomeLayout"
import { Modal } from "components/Modal"
import Yup from "lib/yup"
const __ = gql`
mutation CreateIssueGroup($data: IssueGroupInput!) {
createIssueGroup(data: $data) {
id
title
}
}
query AllIssueGroups {
allIssueGroups {
id
title
}
}
mutation updateIssueGroup($id: String!) {
updateIssueGroup(id: $id) {
id
title
}
}
mutation deleteIssueGroup($id: String!) {
deleteIssueGroup(id: $id) {
id
title
}
}
`
const IssueGroupSchema = Yup.object().shape({
title: Yup.string().required(),
description: Yup.string().required(),
// issues: Yup.string()
})
function IssueGroups() {
const toast = useToast()
// const [search, setSearch] = React.useState("")
// const [selectedIssueGroups, setSelectedIssueGroups] = React.useState<string[]>([])
const modalProps = useDisclosure()
const modalPropsUpdate = useDisclosure()
// // const [sort, setSort] = React.useState<Sort>({ createdAt: SortOrder.Desc })
const [createIssueGroup] = useCreateIssueGroupMutation()
const [updateIssueGroup] = useUpdateIssueGroupMutation()
const [deleteIssueGroup] = useDeleteIssueGroupMutation()
const { data: issueGroups } = useAllIssueGroupsQuery()
const defaultValues = {
title: "",
description: "",
// issues: "",
}
const form = useForm({ defaultValues, schema: IssueGroupSchema })
const [selectedIssueGroup, setSelectedIssueGroup ] = React.useState<Omit<
IssueGroupGQLType,
"createdAt" | "updatedAt" | "issues"
> | null>(null)
const { isOpen, onOpen, onClose } = useDisclosure()
const onCreateSubmit = async (data: Yup.InferType<typeof IssueGroupSchema>) => {
return await form.handler(
() =>
createIssueGroup({ variables: { data } }),
{
onSuccess: async (res, toast) => {
// await refetchAllIssueGroups()
toast({
title: "IssueGroup created",
status: "success",
})
form.reset()
onClose()
}
}
)
}
const handleUpdateSubmit = async (data: Yup.InferType<typeof IssueGroupSchema>) => {
console.log(selectedIssueGroup.id)
return await form.handler(
() =>
updateIssueGroup({ variables: {
id: selectedIssueGroup.id,
// issues: "none",
data: { ...data }
} }),
)
}
const onDeleteIssueGroup = async (id: string) => {
return
form.handler(
() =>
deleteIssueGroup({ variables: { id } }),
{
onSuccess: async () => {
await fetch("api/issueGroups", { method: "delete"})
toast({
title: "IssueGroup deleted",
status: "success",
})
},
// refetchAllIssueGroups()
},
)
}
return (
<Box>
<Head>
<title>trying to figure out how to crud in prisma react hook forms</title>
</Head>
<Wrap mb={4} spacing={2}>
<Button
onClick={modalProps.onOpen}
leftIcon={<Box boxSize="18px" as={VscIssues} />}
color="brand.white"
fontWeight="normal"
backgroundColor="brand.orange"
_hover={{
backgroundColor: "brand.green",
color: "brand.white",
}}
>
Create issueGroup
</Button>
</Wrap>
<Modal {...modalProps} title="Create IssueGroup">
{/* <AdminCreateIssueGroupForm onClose={modalProps.onClose} /> */}
<Form {...form} onSubmit={onCreateSubmit}>
<Stack>
<Input name="title" label="Title" />
{/* <Input name="description" label="Description" /> */}
{/* <Text mb='8px' fontWeight="medium" fontSize="sm" > Description</Text> */}
<Textarea name="description" label="Describe" rows={4} />
<FormError />
<ButtonGroup>
<Button onClick={modalProps.onClose}>Cancel</Button>
<Button
type="submit"
isLoading={form.formState.isSubmitting}
isDisabled={form.formState.isSubmitting}
color="brand.white"
fontWeight="normal"
backgroundColor="brand.orange"
_hover={{
backgroundColor: "brand.green",
color: "brand.white",
}}
>
Create
</Button>
</ButtonGroup>
</Stack>
</Form>
</Modal>
<TableContainer maxW="80%">
<Table variant='simple'>
<Tbody>
{issueGroups?.allIssueGroups.map((issueGroup) => (
<Tr key={issueGroup.id}>
<Td>
<Text textStyle="h6">{issueGroup.title}</Text>
</Td>
<Td>
<ButtonGroup>
<IconButton
variant='outline'
color="brand.blue"
fontWeight="normal"
backgroundColor="brand.white"
aria-label='Update Issue Group'
fontSize='15px'
ml={4}
icon={<CgEditMarkup />}
// onClick={modalPropsUpdate.onOpen}
onClick={() => {
setSelectedIssueGroup(issueGroup)
modalPropsUpdate.onOpen()
}}
_hover={{
backgroundColor: "brand.blue",
color: "brand.white",
}}
/>
{/* {
selectedIssueGroup && isOpen &&
<Modal issueGroup={selectedIssueGroup} isOpen={isOpen} onClose={onClose} title="Edit IssueGroup" >
} */}
<Modal {...modalPropsUpdate } title="Edit IssueGroup" >
{/* <AdminUpdateIssueGroupForm
onClose={modalPropsUpdate.onClose}
issueGroup={selectedIssueGroup} */}
{/* /> */}
<Form {...form} onSubmit={handleUpdateSubmit}>
<Stack>
<Input name="title" label="Title" defaultValue={selectedIssueGroup.title} />
<Textarea name="description" label="Describe" rows={4} defaultValue={selectedIssueGroup.description} />
// I also tried using the defaultValue as issueGroup.title/description, but those don't load the values either. When I try with issueGroup.title, the modal loads with empty fields (there are values saved in psql). When I try with selectedIssueGroup.title, the error message says:
TypeError: Cannot read properties of null (reading 'title').
I know there is a title because it renders on the page when I list existing objects. I also know the update is working because I can see psql update.
<FormError />
<ButtonGroup>
<Button onClick={modalPropsUpdate.onClose}>Cancel</Button>
<Button
type="submit"
isLoading={form.formState.isSubmitting}
isDisabled={form.formState.isSubmitting}
color="brand.white"
fontWeight="normal"
backgroundColor="brand.orange"
_hover={{
backgroundColor: "brand.green",
color: "brand.white",
}}
>
Save changes
</Button>
</ButtonGroup>
</Stack>
</Form>
</Modal>
<IconButton
variant='outline'
color="brand.tomato"
fontWeight="normal"
backgroundColor="brand.white"
aria-label='Call Sage'
fontSize='15px'
ml={4}
icon={<CgTrash />}
onClick={() => onDeleteIssueGroup(issueGroup.id)}
_hover={{
backgroundColor: "brand.tomato",
color: "brand.white",
}}
/>
</ButtonGroup>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
)
}
IssueGroups.getLayout = (page: React.ReactNode) => <AuthedHomeLayout><AdminLayout>{page}</AdminLayout></AuthedHomeLayout>
export default IssueGroups
ARJUN'S SUGGESTIONS
I tried each of Arjun's suggestions in his answer below. The results were as follows:
First suggestion - being to add a use effect statement to the issues.tsx to try and set the value of issueGroup. To do this, I added this effect beneath the definition of setSelectedIssueGroup:
useEffect(()=>{
if(selectedIssueGroup)
modalPropsUpdate.onOpen()
}, [selectedIssueGroup]);
When I try this, I get an error in my vsCode that says:
React Hook useEffect has a missing dependency: 'modalPropsUpdate'.
Either include it or remove the dependency array.
modalPropsUpdate is defined above that useEffect as:
const modalPropsUpdate = useDisclosure()
To address that error, I tried commenting out that line. In that case, the useEffect is:
useEffect(()=>{
if(selectedIssueGroup)
// modalPropsUpdate.onOpen()
}, [selectedIssueGroup]);
The error message says:
expression expected.
I don't know what this means.
When I try to load the local host with this error, the error message is:
Error: x Unexpected token }. Expected this, import, async,
function, [ for array literal, { for object literal, # for decorator,
function, class, null, true, false, number, bigint, string, regexp, `
I think Arjun might be expecting that I would put async and await somewhere in this solution, but I don't know where to put them.
| for template literal, (, or an identifier
The onClick handler for update still has:
onClick={() => {
setSelectedIssueGroup(issueGroup)
modalPropsUpdate.onOpen()
}}
Second suggestion - being to use value instead of defaultValue in the input fields.
When I try this, by removing defaultValue and adding: value={issueGroup?.title} to the inputs, the modal loads, with blank fields, but which update with the saved value when I click inside the input field.
However, I can't update the value even if this UX issue were okay (it isn't). The field becomes fixed and I can't type in it.
Third suggestion - being to define the defaultValues by using setValue. To implement this, I tried:
const { setValue } = useForm();
const defaultValues = {
title: setValue("title", selectedIssueGroup?.title),
description: setValue("description", selectedIssueGroup?.description),
// issues: "",
}
const form = useForm({ defaultValues, schema: IssueGroupSchema })
The modal loads (but with a blacked-out screen behind it, whereas other successful attempts just blur the background), but the saved values are not revealed in the input fields of the update form.
SANDBOX
I tried to make a sandbox for this form here. I don't know how to add the server connections to generate the types or mutations inside the code sandbox, so instead, I copied the prisma db extracts.
Try 1:
Possibly the state update completes after you open the modal because you wrote
setSelectedIssueGroup(issueGroup)
modalPropsUpdate.onOpen() // immediately opening modal
Here state update will take some time to set selectedIssueGroup. before that modal gets opened and you have null as default state value so you might get the error.
You need to open the modal once the state got set. You can achieve this using useEffect.
useEffect(()=>{
if(selectedIssueGroup)
modalPropsUpdate.onOpen()
}, [selectedIssueGroup]);
Try 2:
if your modal gets mounted to DOM even before opening it, then your defaultValue will be set null. In that case you have to use value prop in <Input /> instead of defaultValue to set your values.
and handle null in input like
<Input name="title" label="Title" defaultValue={selectedIssueGroup?.title} />
Try 3:
you can get the setValue from useForm() like this,
const { setValue } = useForm();
and use it to set any field value with name
setValue("title", selectedIssueGroup.title);

React Table Data Update

I am working with react table where I am passing the data as prop to react table component which is a mobx state. Also I am using the pagination in for react table data. The issue is when I am updating the state value from the cell edit of react table the state is get updated and when if move to the next pagination and do some changes there and comeback to the first page and it shows only the old state value only. I have included my coding below. Can anyone please give me a solution?
Parent component where I am calling the React Table as child component.
`/** #format */
/* eslint-disable prefer-const */
/* eslint-disable #typescript-eslint/naming-convention */
import { toJS } from "mobx";
import { observer } from "mobx-react-lite";
import React, { useEffect, useState } from "react";
import { Button, Card, CardBody, Row } from "reactstrap";
import styled from "styled-components";
// eslint-disable-next-line import/no-extraneous-dependencies
import { useLocation } from "react-router";
import Rocket from "../../assets/img/raw/rocket-pngrepo-com.png";
import ReactTable from "../ReactTable";
import CategorySelector from "./CategorySelector";
import useStore from "../../mobx/UseStore";
import UserService from "../../services/UserService";
import NotificationManager from "../common/react-notifications/NotificationManager";
import IntlMessages from "../../helpers/IntlMessages";
import { REFACTORDATA } from "../../constants/refactorData";
import "./animation.scss";
import { START } from "../../constants";
import LogService from "../../services/LogService";
const CardLayout = styled(Card)`
/* border: 1px solid red; */
/* width: 50%; */
`;
const SaveButton = styled(Button)`
/* position: absolute; */
justify-content: end;
padding: 0.5rem 2rem 0.5rem 2rem;
span {
font-size: 2rem;
}
`;
// const TextArea = styled.textarea`
// /* background-color: #151a30; */
// color: #ffffff;
// `;
const OutPutViewSection = () => {
const { UIState, UserState } = useStore();
// const processedData = REFACTORDATA(UIState.processedData);
// finding current screen
function getCurrentScreenName() {
// eslint-disable-next-line react-hooks/rules-of-hooks
const location = useLocation();
//
const { pathname } = location;
const urlParts = pathname.split("/");
const lastPart = urlParts[urlParts.length - 1];
return lastPart;
}
const currentScreen = getCurrentScreenName();
// bring metadata id for updating database
const { metaDataId, metaData } = UIState;
// statelocal for controlling the category and set data origin
const [dataOrigin, setDataOrigin] = useState(metaData ? metaData.dataOrigin : "");
const [isSelectModalOpen, setIsSelectModalOpen] = useState(false);
const toggleSelectModal = () => {
setIsSelectModalOpen(!isSelectModalOpen);
};
// handling change in dataorigin
const onChangeDataOrigin = ({ target }) => {
const { value } = target;
setDataOrigin(value);
};
// table columns
const onSaveData = async () => {
if (!dataOrigin) {
NotificationManager.info("plase-add-data-origin", null, null, null, null, null);
return;
}
let response;
let uType;
let reqData;
if (currentScreen === "manageCVStatus" || currentScreen === "cvStatus") {
// eslint-disable-next-line #typescript-eslint/no-shadow
const { orgId, adminId, templateId, docPath, processType, totalPages } = metaData;
// while updating from metadata userId is populated
let { userId, _id } = metaData;
// eslint-disable-next-line no-underscore-dangle
userId = userId._id;
reqData = {
orgId,
adminId,
userId,
templateId,
metaDataId: UIState.metaDataId,
docPath,
processedData: UIState.processedData,
processType,
totalPages,
dataOrigin,
};
response = await UserService.saveProcessedData(reqData);
// updating only data origin and processed data which may change
UIState.cvMetaDataOperations("update", _id, {
dataOrigin,
processedDataId: { _id: UIState.currentProcessedDataId, processedData: UIState.processedData },
});
UIState.setProcessedData(response.processedData);
// update state with latest data
UIState.updateCvData(response.metaDataId, response.updatedData, response.processedData);
}
if (currentScreen === "cvProcessing" || currentScreen === "cvProcess") {
const { userId, orgId, adminId, userType } = UserState.userData;
uType = userType;
reqData = {
orgId,
adminId,
userId,
templateId: UIState.selectedTemplateId,
metaDataId: UIState.metaDataId,
docPath: UIState.currentFolderPath ? UIState.currentFolderPath : UIState.currentFilePath,
processedData: UIState.processedData,
processType: UIState.processType ? UIState.processType : "-",
totalPages: UIState.totalPages,
dataOrigin,
};
response = await UserService.saveProcessedData(reqData);
}
if (response.success) {
UIState.updateCurrentMetaDataId(response.metaDataId);
NotificationManager.success("Data Saved", "Saved", 1500);
// setting is data saved flag true to prevent deletion of image from server
UIState.updateState("isDataSaved", true);
// after saving data fetch new data
// fetching new data only when processed new cv
if (uType) UIState.fetchMetaData(uType);
}
if (response.error) {
let errorMessage: string = response.error;
if (errorMessage.includes("metaData validation failed")) {
errorMessage = "nofication-name-email-required";
} else if (errorMessage.includes("duplicate key error collection")) {
errorMessage = "notifcation-processed-by-other-user";
} else if (errorMessage.includes("Email is already exists")) {
errorMessage = "notification-email-duplication";
} else {
errorMessage = "notification-error-occured";
}
NotificationManager.error(errorMessage, null, 5000);
}
// set graph status
UserState.setUserGraphApiCallStatus = "false";
};
const validate = (e) => {
e.preventDefault();
if (!dataOrigin) {
NotificationManager.error("data-origin-is-required", null, 5000);
} else {
onSaveData();
}
};
// * cleanup
// eslint-disable-next-line
useEffect(() => {
return () => {
setDataOrigin("");
};
}, []);
const action = "extractProcess";
const templateId = undefined;
// multipage
const onMultiImageProcess = async () => {
// const action = "tempProcess";
const markedData: any = { ...UIState.annotatePages };
const reqData = {
socketId: UIState.socketId,
userId: UserState.userData.userId,
orgId: UserState.userData.orgId,
multiPageInvoiceClasses: markedData,
processType: UIState.processType,
action,
totalPages: UIState.totalPages,
folderPath: UIState.currentFolderPath,
...UIState.templateConfigurations,
templateId,
};
await UserService.processImage(reqData);
};
// PROCESSING SINGLE PAGE
const onSingleImageProcess = async () => {
const markedData = UIState.annotatePages[UIState.currentSelectedPage];
// const action = "tempProcess";
const reqData = {
socketId: UIState.socketId,
userId: UserState.userData.userId,
orgId: UserState.userData.orgId,
imagePath: UIState.currentFilePath,
singlePageInvoiceClasses: [...markedData],
processType: UIState.processType,
action,
totalPages: UIState.totalPages,
...UIState.templateConfigurations,
templateId,
};
await UserService.processImage(reqData);
};
const onProcess = async () => {
try {
// set processing only for cv processing not on create edit
// if (isCreateEditScreen() === false)
UIState.setLoadingState("isProcessing", true);
if (UIState.totalPages === 1) {
await onSingleImageProcess();
}
if (UIState.totalPages > 1) {
await onMultiImageProcess();
}
UIState.increamentCVProcessingProgress(START);
LogService.info("CLASS COORDINATES 🔭 ", toJS(UIState.annotatePages));
// if (isCreateEditScreen()) {
// UIState.setLoadingState("isProcessing", false);
// history.push("/user/manageTemplate");
// }
} catch (error) {
LogService.error(error);
}
};
return (
<>
{/* Rocket animation code here */}
{!UIState.processedData && currentScreen === "cvProcess" && (
<Row>
{/* until auto processing is on going don't show extract data button */}
<div className="d-flex flex-column justify-content-center align-items-center m-auto mb-3">
{/* Extract Data */}
{UIState.isAutoProcessDone && !UIState.processedData && (
<Button color="success" className="font-weight-bold" onClick={onProcess}>
<IntlMessages id="extract-data" />
</Button>
)}
</div>
<div className="OutputViewSection__filler_div" />
<div className="d-flex justify-content-center align-items-center p-5">
<div
className={`w-50 ${UIState.isProcessing ? "vibrate-1" : ""} ${
UIState.isProcessedDataEmmited ? "slide-out-top" : ""
}`}
>
<img src={Rocket} alt="rocket" width="100%" style={{ transform: "rotate(-45deg)" }} />
</div>
<div>
<p className="h4 text-dark">{UIState.cvProcessingProgress}%</p>
</div>
</div>
</Row>
)}
{/* save button */}
<CardLayout className="mb-4" style={{ display: UIState.processedData ? "block" : "none" }}>
<CardBody>
<div className="d-flex flex-column justify-content-center align-items-center mt-3">
{!UIState.isDataSaved && currentScreen === "cvProcess" && (
<p className="h5">
<IntlMessages id="extraction-complated-save-data" />
</p>
)}
<SaveButton color="success" pill className="m-1" size="xl" outline onClick={onSaveData}>
<IntlMessages id="Save" />
</SaveButton>
</div>
<Row className="d-flex flex-column pl-4 pr-4 mb-3">
<p className="h6 font-weight-bold">
<IntlMessages id="cvstatus.data-origin" />
</p>
<textarea value={dataOrigin} onChange={onChangeDataOrigin} />
</Row>
{/* categorey and data origin fields show only when metadataid is set */}
{metaDataId && (
<div className="d-flex justify-content-end mb-3">
{/* <textarea className="textinput" value={category} onChange={onChangeCategory} onBlur={onCategoryFocusLoss} /> */}
{isSelectModalOpen && <CategorySelector isOpen={isSelectModalOpen} toggle={toggleSelectModal} />}
{/* <Select options={options} /> */}
<Button onClick={toggleSelectModal}>
<IntlMessages id="select-category" />
</Button>
</div>
)}
{UIState.processedData && <ReactTable data={UIState.processedData} />}
</CardBody>
</CardLayout>
</>
);
};
export default observer(OutPutViewSection);`
`
ReactTable.tsx
`/** #format */
/* eslint-disable react/jsx-props-no-spreading */
import { observer } from "mobx-react-lite";
import React from "react";
import { usePagination, useTable } from "react-table";
import { Pagination, PaginationItem, PaginationLink, Table } from "reactstrap";
// import Pagination from "../../containers/pages/Pagination";
import useStore from "../../mobx/UseStore";
import EditableCell from "./components/EditableCell";
import { ReactTableStyle } from "./Style";
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
// export default function Index({ columns, data, updateMyData, skipPageReset }) {
function Index({ data }) {
const { UIState } = useStore();
// table columns
const columns = React.useMemo(
() => [
{
Header: "Label",
accessor: "label",
},
{
Header: "Data",
accessor: "data",
},
// { Header: "Action", accessor: "action" },
],
[]
);
const [skipPageReset, setSkipPageReset] = React.useState(false);
const updateMyData = (rowIndex, columnId, value, isChecked) => {
// We also turn on the flag to not reset the page
setSkipPageReset(true);
UIState.updateProcessedData({ rowIndex, columnId, value, isChecked });
};
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
// Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
defaultColumn,
autoResetPage: !skipPageReset,
initialState: { pageIndex: 0, pageSize: 5 },
pageCount: 2,
updateMyData,
},
usePagination
);
// useEffect(() => {
// setPageSize(2);
// }, []);
return (
<ReactTableStyle>
<div className="tableWrap">
<Table {...getTableProps()}>
{/* <thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column: any) => (
<th
{...column.getHeaderProps({
className: column.collapse ? "collapse" : "",
})}
>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead> */}
<tbody {...getTableBodyProps()}>
{console.log()}
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell: any) => (
// <td
// {...cell.getCellProps({
// // className: cell.column.collapse ? "collapse" : "",
// className: "d-flex",
// })}
// >
<div>{cell.render("Cell")}</div>
// </td>
))}
{row.cells.map((cell) => {
console.log(cell);
return "";
})}
</tr>
);
})}
</tbody>
</Table>
</div>
<div className="Footer">
{/* <Pagination
currentPage={pageIndex - 1}
firstIsActive={!canPreviousPage}
lastIsActive={!canNextPage}
numberLimit={3}
totalPage={pageCount}
onChangePage={gotoPage}
/> */}
<Pagination aria-label="Page navigation " listClassName="justify-content-center" size="sm">
<PaginationItem>
<PaginationLink className="first" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
<i className="simple-icon-control-start" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="prev" onClick={() => previousPage()} disabled={!canPreviousPage}>
<i className="simple-icon-arrow-left" />
</PaginationLink>
</PaginationItem>
{/* {[...new Array(pageOptions.length).keys()].map((pageNumber) => (
<PaginationItem active={pageIndex === pageNumber}>
<PaginationLink onClick={() => gotoPage(pageNumber)}>{pageNumber + 1}</PaginationLink>
</PaginationItem>
))} */}
<PaginationItem active>
<PaginationLink onClick={() => gotoPage(pageIndex)}>{pageIndex + 1}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="next" onClick={() => nextPage()} disabled={!canNextPage}>
<i className="simple-icon-arrow-right" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="last" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
<i className="simple-icon-control-end" />
</PaginationLink>
</PaginationItem>
</Pagination>
</div>
</ReactTableStyle>
);
}
export default observer(Index);
`
The child component that I am using as cell.
`/`** #format */
import { InputGroup, InputGroupAddon, InputGroupText, Input } from "reactstrap";
import React from "react";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import useStore from "../../../mobx/UseStore";
import IntlMessages from "../../../helpers/IntlMessages";
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index, original },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
const { UIState } = useStore();
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
// const [check, setCheck] = React.useState(false);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value, true);
// setCheck(true);
};
// If the initialValue is changed external, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<>
{/* adding editable text only for data not for label */}
{id === "label" && UIState.labelKeyMap[value] && (
<div className="font-weight-bold m-3">
{/* <IntlMessages id={`${value}`} /> */}
<IntlMessages id={`${UIState.labelKeyMap[value]}`} />
</div>
)}
{id === "data" && (
<InputGroup className="mb-3">
<InputGroupAddon addonType="prepend">
<InputGroupText>
<Input
addon
color="secondary"
type="checkbox"
aria-label="Checkbox for following text input"
checked={original.isChecked}
/>
</InputGroupText>
</InputGroupAddon>
<Input type="textarea" className="text-dark" width="100" value={value} onChange={onChange} onBlur={onBlur} />
</InputGroup>
)}
</>
);
};
export default observer(EditableCell);
`
The function that update the mobx state in another file.
`updateProcessedData({ rowIndex, columnId, value, isChecked }) {
console.log(`processed data -> ${toJS(JSON.stringify(this.processedData))}`);
this.processedData.forEach((row: any, index) => {
if (index === rowIndex) {
// updating the index of process data obj as with map not working
this.processedData[index] = {
...this.processedData[rowIndex],
[columnId]: value,
isChecked,
};
}
console.log(`processed after -> ${toJS(JSON.stringify(this.processedData))}`);
return row;
});
}`
I want to persist the data that I made changes in the page even i am navigating to other pages

trying to delete from an API using axios and React hooks

Hello im trying to delete a Booking from an api using an input with the ID . obviously something is wrong. I tried to convert my class to a HOC and i just cant get it to work. Right now i cant even type in the textbox .
I know i have severals errors but i dont know how to solve them would appreciate some help. the only relevant parts in the HTML is the form.
const DeleteBooking = () => {
const [ModalIsOpen, SetModalIsOpen] = useState(false); // set false if closen when open
const [booking, setDelete] = useState([]);
const handleChange = (e) => {
setDelete({ [e.target.name]: e.target.value });
};
useEffect((UserIdInput) => {
const bookingId = UserIdInput.target.elements.bookingId.value;
Axios.delete(`https://localhost:44366/api/Products/${bookingId}`) // change api key
.then((response) => {
console.log(response);
setDelete(response.data);
});
}, []);
return (
<>
<div className="App-header">
<button onClick={() => SetModalIsOpen(true)}>Radera bokning</button>
</div>
<Modal
isOpen={ModalIsOpen}
onRequestClose={() => SetModalIsOpen(false)}
style={{
overlay: {
background:
"linear-gradient(-500deg, #ee7752, #6e1b3b, #0c495f, #000000)",
},
content: {
color: "black",
textAlign: "center",
},
}}
>
<div>
<h1>Radera Bokning</h1>
<p style={{ marginTop: "20px" }}>
Vänligen ange ditt bokningsNummer för att radera din bokning
</p>
<form onSubmit={() => setDelete}>
<input
onChange={handleChange}
type="text"
name=" bookingId"
placeholder="BokningsID"
value="bookingId"
></input>
<button type="submit"></button>
</form>
<button
style={{ marginTop: "100px" }}
onClick={() => SetModalIsOpen(false)}
>
Tillbaka
</button>
</div>
</Modal>
</>
);
};
export default DeleteBooking;
Here is an incredibly simple example (working sandbox) that you can build upon:
import Axios from "axios";
import React, { useState } from "react";
// => Component Code
// -> This component will be used to delete posts
export default function App() {
// => State
const [readPostId, writePostId] = useState("");
const [readStatus, writeStatus] = useState("");
// => Handlers
const updatePostId = (e) => writePostId(e.target.value);
const deletePost = async (e) => {
e.preventDefault();
try {
await Axios.delete(`${API_ENDPOINT}/${readPostId}`);
writeStatus("Post successfully deleted");
setTimeout(() => writeStatus(""), 3000);
} catch (err) {
writeStatus("Post deletion failed");
}
};
return (
<div>
<h1>Delete Posts Page</h1>
<h2>Enter your Post ID:</h2>
<em>Press 'Delete' without entering a number to cause an error</em>
<form onSubmit={deletePost}>
<input onChange={updatePostId} value={readPostId} />
<input type="submit" value="Delete" />
</form>
{readStatus && <p>{readStatus}</p>}
</div>
);
}
// => Support Code
const API_ENDPOINT = "https://jsonplaceholder.typicode.com/posts";

How to manage react state for a list of JSX.Elements correctly

I am using react-hooks to manage a list of JSX.Elements. However, once the element changed, trying to delete it will cause unexpected behavior.
I had tried using useReducer, remove by index etc, still unexpected updated result occurred.
FolderPage.tsx
import React, { useState, useEffect } from 'react';
import { Button, Box, Grid } from 'grommet';
import { Add, Close } from 'grommet-icons';
import { Files } from '../Component/Files/Files';
import { ePub } from '../extension/ePub/ePub';
interface Props {}
export const FolderPage: React.FC<Props> = () => {
const [state, setState] = useState([<Files openFileHandlers={[ePub]} />]);
const newFolderPanel = () => setState(prev => prev.concat(<Files openFileHandlers={[ePub]} />));
const removePanel = (panel: JSX.Element) => setState(prevState => prevState.filter(s => s !== panel));
return (
<div>
<Box align="start" pad="xsmall">
<Button icon={<Add />} label="New Folder Panel" onClick={newFolderPanel} primary />
</Box>
{state.map((s, index) => (
<Grid key={index}>
<Box align="end">
<Button
icon={<Close color="white" style={{ backgroundColor: 'red', borderRadius: '50%', padding: '.25rem' }} />}
type="button"
onClick={() => removePanel(s)}
/>
</Box>
{s}
</Grid>
))}
</div>
);
};
For example, in usage:
What should I change my code so my delete click will delete the matched element?
There is a way to work around it. For each item inside the array. Instead of storing item directly, stored it as {id: yourAssignedNumber, content: item}.
In this way, you could have control of the id, and remove by comparing the id only. This way, it will work correctly.
import React, { useState, useRef } from 'react';
import { Button, Row, Col } from 'antd';
import { Files } from '../Components/Files/Files';
import { fileHandler } from '../model/fileHandler';
interface Props {
fileHandlers?: fileHandler[];
}
export const FolderPage: React.FC<Props> = ({ fileHandlers }) => {
const [state, setState] = useState([{ key: -1, content: <Files fileHandlers={fileHandlers} /> }]);
const key = useRef(0);
const newFolderPanel = () =>
setState(prev =>
prev.concat({
key: key.current++,
content: <Files fileHandlers={fileHandlers} />
})
);
const removePanel = (key: number) => setState(prevState => prevState.filter(s => s.key !== key));
return (
<Row>
<Button type="primary" icon="plus" onClick={newFolderPanel} style={{ margin: '.75rem' }}>
New Foldr Panel
</Button>
{state.map(({ key, content }) => (
<Col key={key}>
<div
style={{
background: '#75ff8133',
display: 'grid',
justifyItems: 'end',
padding: '.5rem 1rem'
}}>
<Button onClick={() => removePanel(key)} icon="close" type="danger" shape="circle" />
</div>
{content}
</Col>
))}
</Row>
);
};

Resources