mirror of
https://github.com/strapi/strapi.git
synced 2025-11-30 09:01:16 +00:00
Merge pull request #15900 from strapi/fix/relations-caching-between-locales
This commit is contained in:
commit
3234fcc61d
@ -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`
|
||||||
|
|||||||
@ -53,6 +53,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "date"
|
"type": "date"
|
||||||
|
},
|
||||||
|
"relation_locales": {
|
||||||
|
"type": "relation",
|
||||||
|
"relation": "manyToMany",
|
||||||
|
"target": "api::relation-locale.relation-locale",
|
||||||
|
"mappedBy": "categories"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* relation-locale controller
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { createCoreController } = require('@strapi/strapi').factories;
|
||||||
|
|
||||||
|
module.exports = createCoreController('api::relation-locale.relation-locale');
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* relation-locale router
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { createCoreRouter } = require('@strapi/strapi').factories;
|
||||||
|
|
||||||
|
module.exports = createCoreRouter('api::relation-locale.relation-locale');
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* relation-locale service
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { createCoreService } = require('@strapi/strapi').factories;
|
||||||
|
|
||||||
|
module.exports = createCoreService('api::relation-locale.relation-locale');
|
||||||
@ -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.
|
||||||
|
|||||||
@ -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(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@ -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' });
|
||||||
|
|
||||||
|
|||||||
@ -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 = [
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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(() => {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user