Merge pull request #15900 from strapi/fix/relations-caching-between-locales

This commit is contained in:
Josh 2023-03-03 15:42:28 +00:00 committed by GitHub
commit 3234fcc61d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 153 additions and 69 deletions

View File

@ -163,7 +163,7 @@ would otherwise have a negative impact on the overall performance of the content
This hook takes care of data-fetching and normalizes results relations aswell as search-results. This hook takes care of data-fetching and normalizes results relations aswell as search-results.
```ts ```ts
const { relations: RelationResults, search: RelationResults, searchFor } = useRelation(reactQueryCacheKey: string, options: Options); const { relations: RelationResults, search: RelationResults, searchFor } = useRelation(reactQueryCacheKey: Array<string | object>, options: Options);
``` ```
### `Options` ### `Options`
@ -208,7 +208,7 @@ type RelationResult = {
href?: string; // based on `shouldAddLink` and the `targetModel` href?: string; // based on `shouldAddLink` and the `targetModel`
publicationState: 'draft' | 'published'; publicationState: 'draft' | 'published';
mainField: string; // will fallback to "id" if not set mainField: string; // will fallback to "id" if not set
} };
``` ```
#### `relations` #### `relations`

View File

@ -53,6 +53,12 @@
} }
}, },
"type": "date" "type": "date"
},
"relation_locales": {
"type": "relation",
"relation": "manyToMany",
"target": "api::relation-locale.relation-locale",
"mappedBy": "categories"
} }
} }
} }

View File

@ -0,0 +1,56 @@
{
"kind": "collectionType",
"collectionName": "relation_locales",
"info": {
"singularName": "relation-locale",
"pluralName": "relation-locales",
"displayName": "Relations",
"description": ""
},
"options": {
"draftAndPublish": true
},
"pluginOptions": {
"i18n": {
"localized": true
}
},
"attributes": {
"categories": {
"type": "relation",
"relation": "manyToMany",
"target": "api::category.category",
"inversedBy": "relation_locales"
},
"title": {
"type": "string",
"pluginOptions": {
"i18n": {
"localized": true
}
}
},
"repeatable_relations": {
"type": "component",
"repeatable": true,
"pluginOptions": {
"i18n": {
"localized": true
}
},
"component": "basic.relation"
},
"dynamic_relations": {
"pluginOptions": {
"i18n": {
"localized": true
}
},
"type": "dynamiczone",
"components": [
"basic.relation",
"basic.simple"
]
}
}
}

View File

@ -0,0 +1,9 @@
'use strict';
/**
* relation-locale controller
*/
const { createCoreController } = require('@strapi/strapi').factories;
module.exports = createCoreController('api::relation-locale.relation-locale');

View File

@ -0,0 +1,9 @@
'use strict';
/**
* relation-locale router
*/
const { createCoreRouter } = require('@strapi/strapi').factories;
module.exports = createCoreRouter('api::relation-locale.relation-locale');

View File

@ -0,0 +1,9 @@
'use strict';
/**
* relation-locale service
*/
const { createCoreService } = require('@strapi/strapi').factories;
module.exports = createCoreService('api::relation-locale.relation-locale');

View File

@ -156,7 +156,9 @@ const reducer = (state, action) =>
const initialDataRelations = get(state, initialDataPath); const initialDataRelations = get(state, initialDataPath);
const modifiedDataRelations = get(state, modifiedDataPath); const modifiedDataRelations = get(state, modifiedDataPath);
const valuesToLoad = value.filter((relation) => { const valuesToLoad = !initialDataRelations
? value
: value.filter((relation) => {
return !initialDataRelations.some((initialDataRelation) => { return !initialDataRelations.some((initialDataRelation) => {
return initialDataRelation.id === relation.id; return initialDataRelation.id === relation.id;
}); });
@ -282,9 +284,7 @@ const reducer = (state, action) =>
* relationalFieldPaths won't be an array which is what we're expecting * relationalFieldPaths won't be an array which is what we're expecting
* Therefore we reset these bits of state to the correct data type * Therefore we reset these bits of state to the correct data type
* which is an array. Hence why we replace those fields. * which is an array. Hence why we replace those fields.
*
*/ */
const mergeDataWithPreparedRelations = relationalFieldPaths const mergeDataWithPreparedRelations = relationalFieldPaths
.map((path) => path.split('.')) .map((path) => path.split('.'))
.reduce((acc, currentPaths) => { .reduce((acc, currentPaths) => {
@ -301,7 +301,14 @@ const reducer = (state, action) =>
return result; return result;
}, existingComponents); }, existingComponents);
if (state.modifiedData && get(state.modifiedData, componentName)) { if (
state.modifiedData &&
get(state.modifiedData, componentName) &&
/**
* Only replace the state if the entity ids are the same.
*/
state.modifiedData?.id === initialValues?.id
) {
/** /**
* this will be null on initial load, however subsequent calls * this will be null on initial load, however subsequent calls
* will have data in them correlating to the names of the relational fields. * will have data in them correlating to the names of the relational fields.

View File

@ -107,8 +107,7 @@ const RelationInput = ({
[totalNumberOfRelations, numberOfRelationsToDisplay] [totalNumberOfRelations, numberOfRelationsToDisplay]
); );
const shouldDisplayLoadMoreButton = const shouldDisplayLoadMoreButton = !!labelLoadMore && paginatedRelations.hasNextPage;
(!!labelLoadMore && paginatedRelations.hasNextPage) || paginatedRelations.isLoading;
const options = useMemo( const options = useMemo(
() => () =>

View File

@ -295,23 +295,6 @@ describe('Content-Manager || RelationInput', () => {
expect(screen.queryByText('Load more')).not.toBeInTheDocument(); expect(screen.queryByText('Load more')).not.toBeInTheDocument();
}); });
test('should display load more button loading if there is no next page but loading is true', () => {
setup({
relations: {
data: [],
isLoading: true,
isSuccess: true,
hasNextPage: false,
isFetchingNextPage: false,
},
});
expect(screen.getByRole('button', { name: 'Load more' })).toHaveAttribute(
'aria-disabled',
'true'
);
});
test('should display error state', () => { test('should display error state', () => {
setup({ error: 'This is an error' }); setup({ error: 'This is an error' });

View File

@ -57,8 +57,9 @@ export const RelationInputDataManager = ({
const currentLastPage = Math.ceil(get(initialData, name, []).length / RELATIONS_TO_DISPLAY); const currentLastPage = Math.ceil(get(initialData, name, []).length / RELATIONS_TO_DISPLAY);
const cacheKey = `${slug}-${initialDataPath.join('.')}`; const { relations, search, searchFor } = useRelation(
const { relations, search, searchFor } = useRelation(cacheKey, { [slug, initialDataPath.join('.'), modifiedData.id, defaultParams],
{
relation: { relation: {
enabled: !!endpoints.relation, enabled: !!endpoints.relation,
endpoint: endpoints.relation, endpoint: endpoints.relation,
@ -87,11 +88,16 @@ export const RelationInputDataManager = ({
pageParams: { pageParams: {
...defaultParams, ...defaultParams,
// eslint-disable-next-line no-nested-ternary // eslint-disable-next-line no-nested-ternary
entityId: isCreatingEntry ? undefined : isComponentRelation ? componentId : initialData.id, entityId: isCreatingEntry
? undefined
: isComponentRelation
? componentId
: initialData.id,
pageSize: SEARCH_RESULTS_TO_DISPLAY, pageSize: SEARCH_RESULTS_TO_DISPLAY,
}, },
}, },
}); }
);
const isMorph = useMemo(() => relationType.toLowerCase().includes('morph'), [relationType]); const isMorph = useMemo(() => relationType.toLowerCase().includes('morph'), [relationType]);
const toOneRelation = [ const toOneRelation = [

View File

@ -189,7 +189,7 @@ describe('RelationInputDataManager', () => {
}); });
expect(useRelation).toBeCalledWith( expect(useRelation).toBeCalledWith(
expect.any(String), expect.arrayContaining([expect.any(String)]),
expect.objectContaining({ expect.objectContaining({
search: expect.objectContaining({ search: expect.objectContaining({
pageParams: expect.objectContaining({ pageParams: expect.objectContaining({

View File

@ -33,7 +33,7 @@ const ComponentFixture = ({ children }) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider> <QueryClientProvider client={client}>{children}</QueryClientProvider>
); );
const cacheKey = 'useRelation-cache-key'; const cacheKey = ['useRelation-cache-key'];
function setup(args) { function setup(args) {
return new Promise((resolve) => { return new Promise((resolve) => {
act(() => { act(() => {

View File

@ -49,7 +49,7 @@ export const useRelation = (cacheKey, { relation, search }) => {
const { onLoad: onLoadRelations, normalizeArguments = {} } = relation; const { onLoad: onLoadRelations, normalizeArguments = {} } = relation;
const relationsRes = useInfiniteQuery(['relation', cacheKey], fetchRelations, { const relationsRes = useInfiniteQuery(['relation', ...cacheKey], fetchRelations, {
cacheTime: 0, cacheTime: 0,
enabled: relation.enabled, enabled: relation.enabled,
/** /**
@ -138,7 +138,7 @@ export const useRelation = (cacheKey, { relation, search }) => {
}, [status, onLoadRelationsCallback, data]); }, [status, onLoadRelationsCallback, data]);
const searchRes = useInfiniteQuery( const searchRes = useInfiniteQuery(
['relation', cacheKey, 'search', JSON.stringify(searchParams)], ['relation', ...cacheKey, 'search', JSON.stringify(searchParams)],
fetchSearch, fetchSearch,
{ {
enabled: Object.keys(searchParams).length > 0, enabled: Object.keys(searchParams).length > 0,