Fix cleanup in useeffect

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2020-12-01 17:23:12 +01:00
parent 62bb0bbae1
commit 16c7d2123f
4 changed files with 120 additions and 37 deletions

View File

@ -1,5 +1,5 @@
import { memo, useCallback, useEffect, useMemo, useRef, useReducer } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useReducer } from 'react';
import { useParams, useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { get } from 'lodash'; import { get } from 'lodash';
import { request, useGlobalContext } from 'strapi-helper-plugin'; import { request, useGlobalContext } from 'strapi-helper-plugin';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -15,11 +15,10 @@ import { crudInitialState, crudReducer } from '../../sharedReducers';
import { getRequestUrl } from './utils'; import { getRequestUrl } from './utils';
// This container is used to handle the CRUD // This container is used to handle the CRUD
const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug }) => { const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug, id, origin }) => {
const { emitEvent } = useGlobalContext(); const { emitEvent } = useGlobalContext();
const { push, replace } = useHistory(); const { push, replace } = useHistory();
const { id, origin } = useParams();
const [ const [
{ componentsDataStructure, contentTypeDataStructure, data, isLoading, status }, { componentsDataStructure, contentTypeDataStructure, data, isLoading, status },
dispatch, dispatch,
@ -99,8 +98,6 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug }) => {
}); });
}, [allLayoutData]); }, [allLayoutData]);
const shouldFetch = useRef(true);
useEffect(() => { useEffect(() => {
const abortController = new AbortController(); const abortController = new AbortController();
const { signal } = abortController; const { signal } = abortController;
@ -139,7 +136,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug }) => {
} }
}; };
if (requestURL && shouldFetch.current) { if (requestURL) {
getData(signal); getData(signal);
} else { } else {
dispatch({ type: 'INIT_FORM' }); dispatch({ type: 'INIT_FORM' });
@ -147,7 +144,6 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug }) => {
return () => { return () => {
abortController.abort(); abortController.abort();
shouldFetch.current = requestURL === null;
}; };
}, [requestURL, push, from, cleanReceivedData, cleanClonedData]); }, [requestURL, push, from, cleanReceivedData, cleanClonedData]);
@ -315,6 +311,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, from, slug }) => {
CollectionTypeFormWrapper.defaultProps = { CollectionTypeFormWrapper.defaultProps = {
from: '/', from: '/',
origin: null,
}; };
CollectionTypeFormWrapper.propTypes = { CollectionTypeFormWrapper.propTypes = {
@ -335,6 +332,8 @@ CollectionTypeFormWrapper.propTypes = {
}).isRequired, }).isRequired,
children: PropTypes.func.isRequired, children: PropTypes.func.isRequired,
from: PropTypes.string, from: PropTypes.string,
id: PropTypes.string.isRequired,
origin: PropTypes.string,
slug: PropTypes.string.isRequired, slug: PropTypes.string.isRequired,
}; };

View File

@ -1,5 +1,7 @@
import React, { memo, useMemo } from 'react'; import React, { memo, useMemo } from 'react';
import { Switch, Route, useRouteMatch, useParams } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import { get } from 'lodash';
import PropTypes from 'prop-types';
import { LoadingIndicatorPage, CheckPagePermissions } from 'strapi-helper-plugin'; import { LoadingIndicatorPage, CheckPagePermissions } from 'strapi-helper-plugin';
import pluginPermissions from '../../permissions'; import pluginPermissions from '../../permissions';
import { ContentTypeLayoutContext } from '../../contexts'; import { ContentTypeLayoutContext } from '../../contexts';
@ -10,9 +12,12 @@ import EditSettingsView from '../EditSettingsView';
import ListView from '../ListView'; import ListView from '../ListView';
import ListSettingsView from '../ListSettingsView'; import ListSettingsView from '../ListSettingsView';
const CollectionTypeRecursivePath = () => { const CollectionTypeRecursivePath = ({
const { url } = useRouteMatch(); match: {
const { slug } = useParams(); params: { slug },
url,
},
}) => {
const { isLoading, layout, updateLayout } = useFetchContentTypeLayout(slug); const { isLoading, layout, updateLayout } = useFetchContentTypeLayout(slug);
const { rawContentTypeLayout, rawComponentsLayouts } = useMemo(() => { const { rawContentTypeLayout, rawComponentsLayouts } = useMemo(() => {
@ -34,12 +39,37 @@ const CollectionTypeRecursivePath = () => {
return { rawContentTypeLayout, rawComponentsLayouts }; return { rawContentTypeLayout, rawComponentsLayouts };
}, [layout]); }, [layout]);
if (isLoading) { const uid = get(layout, ['contentType', 'uid'], null);
// This statement is needed in order to prevent the CollectionTypeFormWrapper effects clean up phase to be run twice.
// What can happen is that when navigating from one entry to another the cleanup phase of the fetch data effect is run twice : once when
// unmounting, once when the url changes.
// Since it can happen that the layout there's a delay when the layout is being fetched and the url changes adding the uid ! == slug
// statement prevent the component from being mounted and unmounted twice.
if (uid !== slug || isLoading) {
return <LoadingIndicatorPage />; return <LoadingIndicatorPage />;
} }
const renderRoute = (_, Component) => { const renderRoute = (
return <Component slug={slug} layout={layout} />; {
location: { state },
history: { goBack },
match: {
params: { id, origin },
},
},
Component
) => {
return (
<Component
slug={slug}
layout={layout}
state={state}
goBack={goBack}
id={id}
origin={origin}
/>
);
}; };
const routes = [ const routes = [
@ -79,4 +109,13 @@ const CollectionTypeRecursivePath = () => {
); );
}; };
CollectionTypeRecursivePath.propTypes = {
match: PropTypes.shape({
url: PropTypes.string.isRequired,
params: PropTypes.shape({
slug: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
};
export default memo(CollectionTypeRecursivePath); export default memo(CollectionTypeRecursivePath);

View File

@ -1,7 +1,6 @@
import React, { memo, useCallback, useMemo } from 'react'; import React, { memo, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { get } from 'lodash'; import { get } from 'lodash';
import { useHistory, useLocation } from 'react-router-dom';
import { import {
BackHeader, BackHeader,
LiLink, LiLink,
@ -31,10 +30,8 @@ import DeleteLink from './DeleteLink';
import InformationCard from './InformationCard'; import InformationCard from './InformationCard';
/* eslint-disable react/no-array-index-key */ /* eslint-disable react/no-array-index-key */
const EditView = ({ isSingleType, layout, slug }) => { const EditView = ({ isSingleType, goBack, layout, slug, state, id, origin }) => {
const { goBack } = useHistory();
const { currentEnvironment, plugins } = useGlobalContext(); const { currentEnvironment, plugins } = useGlobalContext();
const { state } = useLocation();
// Permissions // Permissions
const viewPermissions = useMemo(() => generatePermissionsObject(slug), [slug]); const viewPermissions = useMemo(() => generatePermissionsObject(slug), [slug]);
const { allowedActions, isLoading: isLoadingForPermissions } = useUserPermissions( const { allowedActions, isLoading: isLoadingForPermissions } = useUserPermissions(
@ -59,7 +56,9 @@ const EditView = ({ isSingleType, layout, slug }) => {
: pluginPermissions.collectionTypesConfigurations; : pluginPermissions.collectionTypesConfigurations;
}, [isSingleType]); }, [isSingleType]);
const configurationsURL = `/plugins/${pluginId}/collectionType/${slug}/configurations/edit`; const configurationsURL = `/plugins/${pluginId}/${
isSingleType ? 'singleType' : 'collectionType'
}/${slug}/configurations/edit`;
const currentContentTypeLayoutData = useMemo(() => get(layout, ['contentType'], {}), [layout]); const currentContentTypeLayoutData = useMemo(() => get(layout, ['contentType'], {}), [layout]);
const DataManagementWrapper = useMemo( const DataManagementWrapper = useMemo(
@ -91,7 +90,7 @@ const EditView = ({ isSingleType, layout, slug }) => {
// TODO: create a hook to handle/provide the permissions this should be done for the i18n feature // TODO: create a hook to handle/provide the permissions this should be done for the i18n feature
return ( return (
<DataManagementWrapper allLayoutData={layout} from={from} slug={slug}> <DataManagementWrapper allLayoutData={layout} from={from} slug={slug} id={id} origin={origin}>
{({ {({
componentsDataStructure, componentsDataStructure,
contentTypeDataStructure, contentTypeDataStructure,
@ -264,7 +263,10 @@ const EditView = ({ isSingleType, layout, slug }) => {
}; };
EditView.defaultProps = { EditView.defaultProps = {
id: null,
isSingleType: false, isSingleType: false,
origin: null,
state: {},
}; };
EditView.propTypes = { EditView.propTypes = {
@ -278,7 +280,11 @@ EditView.propTypes = {
attributes: PropTypes.object.isRequired, attributes: PropTypes.object.isRequired,
}).isRequired, }).isRequired,
}).isRequired, }).isRequired,
id: PropTypes.string,
isSingleType: PropTypes.bool, isSingleType: PropTypes.bool,
goBack: PropTypes.func.isRequired,
origin: PropTypes.string,
state: PropTypes.object,
slug: PropTypes.string.isRequired, slug: PropTypes.string.isRequired,
}; };

View File

@ -1,16 +1,40 @@
import React from 'react'; import React, { memo, useMemo } from 'react';
import { Switch, Route, useRouteMatch, useParams } from 'react-router-dom'; import { Switch, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { LoadingIndicatorPage, CheckPagePermissions } from 'strapi-helper-plugin'; import { LoadingIndicatorPage, CheckPagePermissions } from 'strapi-helper-plugin';
import pluginPermissions from '../../permissions'; import pluginPermissions from '../../permissions';
import { ContentTypeLayoutContext } from '../../contexts'; import { ContentTypeLayoutContext } from '../../contexts';
import { useFetchContentTypeLayout } from '../../hooks'; import { useFetchContentTypeLayout } from '../../hooks';
import { formatLayoutToApi } from '../../utils';
import EditView from '../EditView'; import EditView from '../EditView';
import EditSettingsView from '../EditSettingsView'; import EditSettingsView from '../EditSettingsView';
const SingleTypeRecursivePath = props => { const SingleTypeRecursivePath = ({
const { url } = useRouteMatch(); match: {
const { slug } = useParams(); params: { slug },
const { isLoading, layout } = useFetchContentTypeLayout(slug); url,
},
}) => {
const { isLoading, layout, updateLayout } = useFetchContentTypeLayout(slug);
const { rawContentTypeLayout, rawComponentsLayouts } = useMemo(() => {
let rawComponentsLayouts = {};
let rawContentTypeLayout = {};
if (layout.contentType) {
rawContentTypeLayout = formatLayoutToApi(layout.contentType);
}
if (layout.components) {
rawComponentsLayouts = Object.keys(layout.components).reduce((acc, current) => {
acc[current] = formatLayoutToApi(layout.components[current]);
return acc;
}, {});
}
return { rawContentTypeLayout, rawComponentsLayouts };
}, [layout]);
if (isLoading) { if (isLoading) {
return <LoadingIndicatorPage />; return <LoadingIndicatorPage />;
@ -19,21 +43,36 @@ const SingleTypeRecursivePath = props => {
return ( return (
<ContentTypeLayoutContext.Provider value={layout}> <ContentTypeLayoutContext.Provider value={layout}>
<Switch> <Switch>
<Route path={`${url}/configurations/edit`}>
<CheckPagePermissions permissions={pluginPermissions.singleTypesConfigurations}>
<EditSettingsView
components={rawComponentsLayouts}
isContentTypeView
mainLayout={rawContentTypeLayout}
slug={slug}
updateLayout={updateLayout}
/>
</CheckPagePermissions>
</Route>
<Route <Route
path={`${url}/ctm-configurations/edit-settings/:type`} path={url}
render={routeProps => ( render={({ location: { state }, history: { goBack } }) => {
<CheckPagePermissions permissions={pluginPermissions.singleTypesConfigurations}> return (
<EditSettingsView {...props} {...routeProps} slug={slug} /> <EditView layout={layout} slug={slug} isSingleType state={state} goBack={goBack} />
</CheckPagePermissions> );
)} }}
/>
<Route
path={`${url}`}
render={() => <EditView layout={layout} slug={slug} isSingleType />}
/> />
</Switch> </Switch>
</ContentTypeLayoutContext.Provider> </ContentTypeLayoutContext.Provider>
); );
}; };
export default SingleTypeRecursivePath; SingleTypeRecursivePath.propTypes = {
match: PropTypes.shape({
url: PropTypes.string.isRequired,
params: PropTypes.shape({
slug: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
};
export default memo(SingleTypeRecursivePath);