Skip to content

Commit

Permalink
Merge pull request #774 from open-formulieren/issue/4929-react-router…
Browse files Browse the repository at this point in the history
…-upgrade-part-2

React router upgrade part 2
  • Loading branch information
sergei-maertens authored Jan 17, 2025
2 parents 9768bdd + dd78c91 commit da9810b
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 192 deletions.
41 changes: 3 additions & 38 deletions src/components/Form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {useImmerReducer} from 'use-immer';
import {AnalyticsToolsConfigContext, ConfigContext} from 'Context';
import {destroy, get} from 'api';
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import FormStep from 'components/FormStep';
import Loader from 'components/Loader';
import {ConfirmationView, StartPaymentView} from 'components/PostCompletionViews';
import ProgressIndicator from 'components/ProgressIndicator';
Expand All @@ -29,14 +28,12 @@ import {
STEP_LABELS,
SUBMISSION_ALLOWED,
} from 'components/constants';
import {findNextApplicableStep} from 'components/utils';
import {flagActiveSubmission, flagNoActiveSubmission} from 'data/submissions';
import useAutomaticRedirect from 'hooks/useAutomaticRedirect';
import useFormContext from 'hooks/useFormContext';
import usePageViews from 'hooks/usePageViews';
import useQuery from 'hooks/useQuery';
import useRecycleSubmission from 'hooks/useRecycleSubmission';
import useSessionTimeout from 'hooks/useSessionTimeout';
import Types from 'types';

import FormDisplay from './FormDisplay';
Expand Down Expand Up @@ -152,8 +149,6 @@ const Form = () => {
onSubmissionLoaded
);

const [, expiryDate] = useSessionTimeout();

const {value: analyticsToolsConfigInfo, loading: loadingAnalyticsConfig} = useAsync(async () => {
return await get(`${config.baseUrl}analytics/analytics-tools-config-info`);
}, [intl.locale]);
Expand All @@ -180,16 +175,6 @@ const Form = () => {
setSubmissionId(submission.id);
};

const onStepSubmitted = async formStep => {
const currentStepIndex = form.steps.indexOf(formStep);

const nextStepIndex = findNextApplicableStep(currentStepIndex, state.submission);
const nextStep = form.steps[nextStepIndex]; // will be undefined if it's the last step

const nextUrl = nextStep ? `/stap/${nextStep.slug}` : '/overzicht';
navigate(nextUrl);
};

const onSubmitForm = processingStatusUrl => {
removeSubmissionId();
dispatch({
Expand Down Expand Up @@ -330,15 +315,15 @@ const Form = () => {
path="overzicht"
element={
<ErrorBoundary useCard>
<SessionTrackerModal expiryDate={expiryDate}>
<SessionTrackerModal>
<RequireSubmission
submission={state.submission}
form={form}
retrieveSubmissionFromContext
processingError={state.processingError}
onConfirm={onSubmitForm}
component={SubmissionSummary}
onClearProcessingErrors={() => dispatch({type: 'CLEAR_PROCESSING_ERROR'})}
onDestroySession={onDestroySession}
form={form}
/>
</SessionTrackerModal>
</ErrorBoundary>
Expand Down Expand Up @@ -374,26 +359,6 @@ const Form = () => {
</ErrorBoundary>
}
/>

<Route
path="stap/:step"
element={
<ErrorBoundary useCard>
<SessionTrackerModal expiryDate={expiryDate}>
<RequireSubmission
form={form}
submission={state.submission}
onLogicChecked={submission =>
dispatch({type: 'SUBMISSION_LOADED', payload: submission})
}
onStepSubmitted={onStepSubmitted}
component={FormStep}
onDestroySession={onDestroySession}
/>
</SessionTrackerModal>
</ErrorBoundary>
}
/>
</Routes>
);

Expand Down
24 changes: 13 additions & 11 deletions src/components/FormStep/FormStep.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {getWorker} from 'msw-storybook-addon';
import {withRouter} from 'storybook-addon-remix-react-router';
import {v4 as uuid4} from 'uuid';

import {FormContext} from 'Context';
import {buildForm, buildSubmission} from 'api-mocks';
import {
mockEmailVerificationPost,
mockEmailVerificationVerifyCodePost,
} from 'components/EmailVerification/mocks';
import {SubmissionProvider} from 'components/Form';
import {AnalyticsToolsDecorator, ConfigDecorator} from 'story-utils/decorators';
import {sleep} from 'utils';

Expand All @@ -25,8 +27,7 @@ export default {
component: FormStep,
decorators: [ConfigDecorator, withRouter, AnalyticsToolsDecorator],
args: {
onLogicChecked: fn(),
onStepSubmitted: fn(),
onSubmissionObtained: fn(),
onDestroySession: fn(),
},
argTypes: {
Expand All @@ -53,8 +54,7 @@ const render = ({
// component props
form,
submission,
onLogicChecked,
onStepSubmitted,
onSubmissionObtained,
onDestroySession,
// story args
formioConfiguration,
Expand All @@ -78,13 +78,15 @@ const render = ({
mockEmailVerificationVerifyCodePost
);
return (
<FormStep
form={form}
submission={submission}
onLogicChecked={onLogicChecked}
onStepSubmitted={onStepSubmitted}
onDestroySession={onDestroySession}
/>
<FormContext.Provider value={form}>
<SubmissionProvider
submission={submission}
onSubmissionObtained={onSubmissionObtained}
onDestroySession={onDestroySession}
>
<FormStep />
</SubmissionProvider>
</FormContext.Provider>
);
};

Expand Down
108 changes: 108 additions & 0 deletions src/components/FormStep/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import omit from 'lodash/omit';

import {post, put} from 'api';
import {ValidationError} from 'errors';

/**
* Submits the form step data to the backend.
* @param {string} stepUrl
* @param {Object} data The submission json object.
* @throws {Error} Throws an error if the backend response is not ok.
* @return {Promise}
*/
export const submitStepData = async (stepUrl, data) => {
const stepDataResponse = await put(stepUrl, {data});

if (!stepDataResponse.ok) {
throw new Error(`Backend responded with HTTP ${stepDataResponse.status}`);
}

return stepDataResponse;
};

/**
* Provides a hook to inject custom validations into the submission process.
* @see {@link Form.io documentation} https://help.form.io/developers/form-renderer#customvalidation-submission-next
*
* @param {string} stepUrl
* @param {Function} onBackendError
* @return {Function}
*/
export const getCustomValidationHook = (stepUrl, onBackendError) => {
/**
* The custom validation function.
*
* @param {Object} data The submission data object that is going to be submitted to the server.
* This allows you to alter the submission data object in real time.
* @param {Object} next Called when the beforeSubmit handler is done executing. If you call this
* method without any arguments, like next(), then this means that no errors should be added to
* the default form validation. If you wish to introduce your own custom errors, then you can
* call this method with either a single error object, or an array of errors like the example
* below.
*/
return async (data, next) => {
const PREFIX = 'data';
const validateUrl = `${stepUrl}/validate`;
let validateResponse;

try {
validateResponse = await post(validateUrl, data);
} catch (error) {
if (error instanceof ValidationError) {
// process the errors
const invalidParams = error.invalidParams.filter(param =>
param.name.startsWith(`${PREFIX}.`)
);

const errors = invalidParams.map(({name, code, reason}) => ({
path: name.replace(`${PREFIX}.`, '', 1),
message: reason,
code: code,
}));

next(errors);
return;
} else {
onBackendError(error);
next([{path: '', message: error.detail, code: error.code}]);
return;
}
}

if (!validateResponse.ok) {
console.warn(`Unexpected HTTP ${validateResponse.status}`);
}

next();
};
};

/**
* Submits the form step data to the backend in order te evaluate its logic using the _check-logic
* endpoint.
* @param {string} stepUrl
* @param {Object} data The current form data.
* @param {*[]} invalidKeys
* @param {*} signal
* @throws {Error} Throws an error if the backend response is not ok.
* @return {Promise}
*/
export const doLogicCheck = async (stepUrl, data, invalidKeys = [], signal) => {
const url = `${stepUrl}/_check-logic`;
// filter out the invalid keys so we only send valid (client-side) input data to the
// backend to evaluate logic.
let dataForLogicCheck = invalidKeys.length ? omit(data, invalidKeys) : data;
const stepDetailData = await post(url, {data: dataForLogicCheck}, signal);

if (!stepDetailData.ok) {
throw new Error('Invalid response'); // TODO -> proper error & use ErrorBoundary
}

// Re-add any invalid data to the step data that was not sent for the logic check. Otherwise, any previously saved
// data in the step will overwrite the user input
if (invalidKeys.length) {
Object.assign(stepDetailData.data.step.data, data);
}

return stepDetailData.data;
};
Loading

0 comments on commit da9810b

Please sign in to comment.