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

fix: remove the usage of context to avoid excessive rerender #28

Merged
merged 2 commits into from
Apr 16, 2024
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
2 changes: 1 addition & 1 deletion tdm-be/config/development.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"isHttps": true
},
"external": {
"host": "localhost:8080",
"host": "localhost:5173",
"isHttps": false
}
},
Expand Down
10 changes: 1 addition & 9 deletions tdm-fe/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import WebServicesFooter from '~/app/components/layout/WebServicesFooter';
import WebServicesHeader from '~/app/components/layout/WebServicesHeader';
import ProcessingCreationForm from '~/app/pages/ProcessingCreationForm';
import ProcessingStatus from '~/app/pages/ProcessingStatus';
import ProcessingFormContextProvider from '~/app/provider/ProcessingFormContextProvider';
import { RouteProcessingStatus, RouteRoot } from '~/app/shared/routes';

import Container from '@mui/material/Container';
Expand All @@ -18,14 +17,7 @@ const App = () => {
<WebServicesHeader />
<Container id="app-container">
<Routes>
<Route
path={RouteRoot}
element={
<ProcessingFormContextProvider>
<ProcessingCreationForm />
</ProcessingFormContextProvider>
}
/>
<Route path={RouteRoot} element={<ProcessingCreationForm />} />
<Route path={`${RouteProcessingStatus}/:id`} element={<ProcessingStatus />} />
</Routes>
</Container>
Expand Down
107 changes: 72 additions & 35 deletions tdm-fe/src/app/components/form/ProcessingFormConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,40 @@ import '~/app/components/form/scss/ProcessingFormCommon.scss';
import '~/app/components/form/scss/ProcessingFormConfiguration.scss';
import CircularWaiting from '~/app/components/progress/CircularWaiting';
import Markdown from '~/app/components/text/Markdown';
import { ProcessingFormContext } from '~/app/provider/ProcessingFormContextProvider';

import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import { useContext, useMemo } from 'react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';

import type { SyntheticEvent, FocusEvent } from 'react';
import type { Enrichment, Wrapper } from '~/app/shared/data.types';

const ProcessingFormConfiguration = () => {
const {
wrapperList,
enrichmentList,
fields,
wrapper,
setWrapper,
wrapperParam,
setWrapperParam,
enrichment,
setEnrichment,
isPending,
} = useContext(ProcessingFormContext);
import type { Enrichment, ProcessingFields, Wrapper } from '~/app/shared/data.types';

export type ProcessingFormConfigurationValueType = {
wrapper: Wrapper | null;
wrapperParam: string | null;
enrichment: Enrichment | null;
};

type ProcessingFormConfigurationProps = {
wrapperList: Wrapper[];
enrichmentList: Enrichment[];
fields: ProcessingFields | null;
isPending: boolean;
value: ProcessingFormConfigurationValueType;
onChange: (value: ProcessingFormConfigurationValueType) => void;
};

const ProcessingFormConfiguration = ({
wrapperList,
enrichmentList,
fields,
isPending,
value,
onChange,
}: ProcessingFormConfigurationProps) => {
const [wrapper, setWrapper] = useState<Wrapper | null>(value.wrapper);
const [wrapperParam, setWrapperParam] = useState<string>(value.wrapperParam ?? '');
const [enrichment, setEnrichment] = useState<Enrichment | null>(value.enrichment);

const cleanFields = useMemo(() => {
if (fields && fields.fields) {
Expand All @@ -32,20 +44,50 @@ const ProcessingFormConfiguration = () => {
return [];
}, [fields]);

/**
* Handle event from wrapper selection
*/
const handleWrapperChange = (_: SyntheticEvent, newWrapper: Wrapper | null) => {
useEffect(() => {
let invalid = false;

// Check wrapper
if (wrapperList && !wrapperList.find((entry) => entry.url === wrapper?.url)) {
invalid = true;
}

// Check wrapper param
if (wrapperParam === null || wrapperParam === undefined) {
invalid = true;
}

// Check enrichment
if (enrichmentList && !enrichmentList.find((entry) => entry.url === enrichment?.url)) {
invalid = true;
}

if (!invalid) {
onChange({
wrapper,
wrapperParam,
enrichment,
});
}
}, [enrichment, enrichmentList, onChange, wrapper, wrapperList, wrapperParam]);

const handleWrapperChange = useCallback((_: SyntheticEvent, newWrapper: Wrapper | null) => {
setWrapper(newWrapper);
};
}, []);

const handleWrapperParamChange = useCallback((_: SyntheticEvent, newWrapperParam: string | null) => {
setWrapperParam(newWrapperParam ?? '');
}, []);

const handleWrapperParamChange = (_: SyntheticEvent, newWrapperParam: string | null) => {
setWrapperParam(newWrapperParam);
};
const handleWrapperParamBlur = useCallback((event: FocusEvent) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
setWrapperParam(event.target.value ?? '');
}, []);

const handleEnrichmentChange = (_: SyntheticEvent, newEnrichment: Enrichment | null) => {
const handleEnrichmentChange = useCallback((_: SyntheticEvent, newEnrichment: Enrichment | null) => {
setEnrichment(newEnrichment);
};
}, []);

/**
* Show a loading box will wait for the operations to be fetched
Expand Down Expand Up @@ -81,14 +123,9 @@ const ProcessingFormConfiguration = () => {
<div id="processing-form-wrapper-param-style"></div>
<Autocomplete
className="processing-form-field"
value={wrapperParam ?? ''}
value={wrapperParam}
onChange={handleWrapperParamChange}
onBlur={(event: unknown) => {
handleWrapperParamChange(
{} as unknown as SyntheticEvent,
(event as FocusEvent<HTMLInputElement | HTMLTextAreaElement>).target.value,
);
}}
onBlur={handleWrapperParamBlur}
options={cleanFields}
renderInput={(params) => (
<TextField
Expand Down Expand Up @@ -121,4 +158,4 @@ const ProcessingFormConfiguration = () => {
);
};

export default ProcessingFormConfiguration;
export default memo(ProcessingFormConfiguration);
23 changes: 9 additions & 14 deletions tdm-fe/src/app/components/form/ProcessingFormConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import CircularWaiting from '~/app/components/progress/CircularWaiting';
import { ProcessingFormContext } from '~/app/provider/ProcessingFormContextProvider';
import { RouteProcessingStatus } from '~/app/shared/routes';

import Alert from '@mui/material/Alert';
import Link from '@mui/material/Link';
import { useContext } from 'react';
import { memo } from 'react';
import { useHref } from 'react-router-dom';

export type ProcessingFormConfirmationProps = {
processingId: string;
state: {
status: 202 | 400 | 409 | 428 | 500 | null | undefined;
pending: boolean;
};
processingId: string | null;
status: 202 | 400 | 409 | 428 | 500 | null;
isPending: boolean;
};

const ProcessingFormConfirmation = () => {
const { processingId, isPending, startingStatus } = useContext(ProcessingFormContext);

const ProcessingFormConfirmation = ({ processingId, status, isPending }: ProcessingFormConfirmationProps) => {
const href = useHref(`${RouteProcessingStatus}/${processingId}`);

/**
Expand All @@ -30,7 +25,7 @@ const ProcessingFormConfirmation = () => {
/**
* Show an error if we get empty operations
*/
if (!startingStatus) {
if (!status) {
return (
<Alert severity="error">
Nous ne parvenons pas à contacter le serveur, merci de ré-essayer ultérieurement.
Expand All @@ -41,14 +36,14 @@ const ProcessingFormConfirmation = () => {
/**
* Show an error if we have no processing linked with the given id
*/
if (startingStatus === 428) {
if (status === 428) {
return <Alert severity="error">Nous ne parvenons pas à trouver le fichier lié à ce traitement.</Alert>;
}

/**
* Show an error if we get any other error
*/
if (startingStatus !== 202 && startingStatus !== 409) {
if (status !== 202 && status !== 409) {
return <Alert severity="error">Un problème inattendu est survenu.</Alert>;
}

Expand All @@ -63,4 +58,4 @@ const ProcessingFormConfirmation = () => {
);
};

export default ProcessingFormConfirmation;
export default memo(ProcessingFormConfirmation);
38 changes: 29 additions & 9 deletions tdm-fe/src/app/components/form/ProcessingFormEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
import '~/app/components/form/scss/ProcessingFormCommon.scss';
import { ProcessingFormContext } from '~/app/provider/ProcessingFormContextProvider';

import TextField from '@mui/material/TextField';
import { useContext } from 'react';
import { memo, useCallback, useEffect, useState } from 'react';

import type { ChangeEvent } from 'react';

const ProcessingFormEmail = () => {
const { email, setEmail, isInvalid } = useContext(ProcessingFormContext);
export const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const handleChange = (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
type ProcessingFormEmailProps = {
value: string | null;
onChange: (value: string | null) => void;
};

const ProcessingFormEmail = ({ value, onChange }: ProcessingFormEmailProps) => {
const [email, setEmail] = useState<string>(value ?? '');
const [isInvalid, setIsInvalid] = useState(false);

useEffect(() => {
let invalid = false;

if (!email || !EMAIL_REGEX.test(email)) {
invalid = true;
}

setIsInvalid(invalid);
if (!invalid) {
onChange(email);
}
}, [email, onChange]);

const handleEmailChange = useCallback((event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
setEmail(event.target.value);
};
}, []);

return (
<div className="processing-form-field-group processing-form-field-with-label">
<TextField
value={email ?? ''}
onChange={handleChange}
value={email}
onChange={handleEmailChange}
error={isInvalid}
className="processing-form-field"
label="Adresse électronique"
Expand All @@ -32,4 +52,4 @@ const ProcessingFormEmail = () => {
);
};

export default ProcessingFormEmail;
export default memo(ProcessingFormEmail);
6 changes: 3 additions & 3 deletions tdm-fe/src/app/components/form/ProcessingFormStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
PROCESSING_UPLOAD_STEP,
PROCESSING_UPLOADING_STEP,
PROCESSING_VALIDATION_STEP,
} from '~/app/provider/ProcessingFormContextProvider';
} from '~/app/pages/ProcessingCreationForm';
import { colors } from '~/app/shared/theme';

import CheckIcon from '@mui/icons-material/Check';
Expand All @@ -14,7 +14,7 @@ import StepConnector, { stepConnectorClasses } from '@mui/material/StepConnector
import StepLabel, { stepLabelClasses } from '@mui/material/StepLabel';
import Stepper from '@mui/material/Stepper';
import { styled } from '@mui/material/styles';
import { useMemo } from 'react';
import { memo, useMemo } from 'react';

import type { StepConnectorProps } from '@mui/material/StepConnector';
import type { StepIconProps } from '@mui/material/StepIcon';
Expand Down Expand Up @@ -157,4 +157,4 @@ const ProcessingFormStepper = ({ step = 0 }: ProcessingFormStepperProps) => {
);
};

export default ProcessingFormStepper;
export default memo(ProcessingFormStepper);
36 changes: 29 additions & 7 deletions tdm-fe/src/app/components/form/ProcessingFormUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import FileUpload from '~/app/components/progress/FileUpload';
import { ProcessingFormContext } from '~/app/provider/ProcessingFormContextProvider';
import { colors } from '~/app/shared/theme';

import AttachFileIcon from '@mui/icons-material/AttachFile';
import CloseIcon from '@mui/icons-material/Close';
import mimeTypes from 'mime';
import { MuiFileInput } from 'mui-file-input';
import { useContext, useMemo } from 'react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';

const ProcessingFormUpload = () => {
const { mimes, file, setFile, isPending, isInvalid, isOnError } = useContext(ProcessingFormContext);
type ProcessingFormUploadProps = {
mimes: string[];
value: File | null;
isOnError: boolean;
isPending: boolean;
onChange: (value: File | null) => void;
};

const ProcessingFormUpload = ({ mimes, value, isOnError, isPending, onChange }: ProcessingFormUploadProps) => {
const [file, setFile] = useState<File | null>(value);
const [isInvalid, setIsInvalid] = useState(false);

const stringifiesMineTypes = useMemo(() => {
return mimes.join(', ');
}, [mimes]);

const handleFileChange = (newFile: File | null) => {
useEffect(() => {
let invalid = false;

if (!file || !mimes.includes(mimeTypes.getType(file.name) ?? '')) {
invalid = true;
}

setIsInvalid(invalid);
if (!invalid) {
onChange(file);
}
}, [file, mimes, onChange]);

const handleFileChange = useCallback((newFile: File | null) => {
setFile(newFile);
};
}, []);

if (isPending || isOnError) {
return <FileUpload showError={isOnError} />;
Expand Down Expand Up @@ -62,4 +84,4 @@ const ProcessingFormUpload = () => {
);
};

export default ProcessingFormUpload;
export default memo(ProcessingFormUpload);
Loading
Loading