Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add async onSubmit #233

Merged
merged 3 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ import { ReactSpreadsheetImport } from "react-spreadsheet-import";
isOpen: Boolean
// Called when flow is closed without reaching submit.
onClose: () => void
// Called after user completes the flow. Provides data array, where data keys matches your field keys.
onSubmit: (data, file) => void
// Called after user completes the flow. Provides data array, where data keys matches your field keys.
onSubmit: (data, file) => void | Promise<any>
```

### Fields
Expand Down
25 changes: 22 additions & 3 deletions src/steps/ValidationStep/ValidationStep.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from "react"
import { Box, Button, Heading, ModalBody, Switch, useStyleConfig } from "@chakra-ui/react"
import { Box, Button, Heading, ModalBody, Switch, useStyleConfig, useToast } from "@chakra-ui/react"
import { ContinueButton } from "../../components/ContinueButton"
import { useRsi } from "../../hooks/useRsi"
import type { Meta } from "./types"
Expand All @@ -22,12 +22,14 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
const styles = useStyleConfig(
"ValidationStep",
) as (typeof themeOverrides)["components"]["ValidationStep"]["baseStyle"]
const toast = useToast()

const [data, setData] = useState<(Data<T> & Meta)[]>(initialData)

const [selectedRows, setSelectedRows] = useState<ReadonlySet<number | string>>(new Set())
const [filterByErrors, setFilterByErrors] = useState(false)
const [showSubmitAlert, setShowSubmitAlert] = useState(false)
const [isSubmitting, setSubmitting] = useState(false)

const updateData = useCallback(
async (rows: typeof data, indexes?: number[]) => {
Expand Down Expand Up @@ -96,9 +98,25 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
},
{ validData: [] as Data<T>[], invalidData: [] as Data<T>[], all: data },
)
onSubmit(calculatedData, file)
setShowSubmitAlert(false)
onClose()
setSubmitting(true)
onSubmit(calculatedData, file)
?.then(() => {
onClose()
})
.catch((err: Error) => {
toast({
status: "error",
variant: "left-accent",
position: "bottom-left",
title: `${translations.alerts.submitError.title}`,
description: err?.message || `${translations.alerts.submitError.defaultMessage}`,
isClosable: true,
})
})
.finally(() => {
setSubmitting(false)
})
}
const onContinue = () => {
const invalidData = data.find((value) => {
Expand Down Expand Up @@ -153,6 +171,7 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
/>
</ModalBody>
<ContinueButton
isLoading={isSubmitting}
onContinue={onContinue}
onBack={onBack}
title={translations.validationStep.nextButtonTitle}
Expand Down
64 changes: 64 additions & 0 deletions src/steps/ValidationStep/tests/ValidationStep.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,70 @@ describe("Validation step tests", () => {
})
})

test("Submit data with a successful async return", async () => {
const onSuccess = jest.fn()
const onSubmit = jest.fn(async (): Promise<void> => {
onSuccess()
return Promise.resolve()
})
const onClose = jest.fn()
render(
<Providers theme={defaultTheme} rsiValues={{ ...mockValues, onSubmit, onClose }}>
<ModalWrapper isOpen={true} onClose={() => {}}>
<ValidationStep initialData={[]} file={file} />
</ModalWrapper>
</Providers>,
)

const finishButton = screen.getByRole("button", {
name: "Confirm",
})

await userEvent.click(finishButton)

await waitFor(() => {
expect(onSubmit).toBeCalledWith({ all: [], invalidData: [], validData: [] }, file)
})
await waitFor(() => {
expect(onSuccess).toBeCalled()
expect(onClose).toBeCalled()
})
})

test("Submit data with a unsuccessful async return", async () => {
const ERROR_MESSAGE = "ERROR has occurred"
const onReject = jest.fn()
const onSubmit = jest.fn(async (): Promise<void> => {
onReject()
throw new Error(ERROR_MESSAGE)
})
const onClose = jest.fn()

render(
<Providers theme={defaultTheme} rsiValues={{ ...mockValues, onSubmit, onClose }}>
<ModalWrapper isOpen={true} onClose={() => {}}>
<ValidationStep initialData={[]} file={file} />
</ModalWrapper>
</Providers>,
)

const finishButton = screen.getByRole("button", {
name: "Confirm",
})

await userEvent.click(finishButton)

await waitFor(() => {
expect(onSubmit).toBeCalledWith({ all: [], invalidData: [], validData: [] }, file)
})

const errorToast = await screen.findAllByText(ERROR_MESSAGE, undefined, { timeout: 5000 })

expect(onReject).toBeCalled()
expect(errorToast?.[0]).toBeInTheDocument()
expect(onClose).not.toBeCalled()
})

test("Filters rows with required errors", async () => {
const UNIQUE_NAME = "very unique name"
const fields = [
Expand Down
4 changes: 4 additions & 0 deletions src/translationsRSIProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export const translations = {
cancelButtonTitle: "Cancel",
finishButtonTitle: "Submit",
},
submitError: {
title: "Error",
defaultMessage: "An error occurred while submitting data",
},
unmatchedRequiredFields: {
headerTitle: "Not all columns matched",
bodyText: "There are required columns that are not matched or ignored. Do you want to continue?",
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export type RsiProps<T extends string> = {
rowHook?: RowHook<T>
// Runs after column matching and on entry change
tableHook?: TableHook<T>
// Function called after user finishes the flow
onSubmit: (data: Result<T>, file: File) => void
// Function called after user finishes the flow. You can return a promise that will be awaited.
onSubmit: (data: Result<T>, file: File) => void | Promise<any>
// Allows submitting with errors. Default: true
allowInvalidSubmit?: boolean
// Enable navigation in stepper component and show back button. Default: false
Expand Down
Loading