Skip to content

Commit

Permalink
refactor: Simplify react-query hooks + fix types
Browse files Browse the repository at this point in the history
  • Loading branch information
yusuf-musleh committed Nov 24, 2023
1 parent ec2c842 commit bdc8d30
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 187 deletions.
25 changes: 7 additions & 18 deletions src/content-tags-drawer/ContentTagsDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import messages from './messages';
import ContentTagsCollapsible from './ContentTagsCollapsible';
import { extractOrgFromContentId } from './utils';
import {
useContentTaxonomyTagsDataResponse,
useIsContentTaxonomyTagsDataLoaded,
useContentDataResponse,
useIsContentDataLoaded,
useContentTaxonomyTagsData,
useContentData,
} from './data/apiHooks';
import { useTaxonomyListDataResponse, useIsTaxonomyListDataLoaded } from '../taxonomy/data/apiHooks';
import Loading from '../generic/Loading';
Expand All @@ -24,26 +22,17 @@ const ContentTagsDrawer = () => {

const org = extractOrgFromContentId(contentId);

const useContentData = () => {
const contentData = useContentDataResponse(contentId);
const isContentDataLoaded = useIsContentDataLoaded(contentId);
return { contentData, isContentDataLoaded };
};

const useContentTaxonomyTagsData = () => {
const contentTaxonomyTagsData = useContentTaxonomyTagsDataResponse(contentId);
const isContentTaxonomyTagsLoaded = useIsContentTaxonomyTagsDataLoaded(contentId);
return { contentTaxonomyTagsData, isContentTaxonomyTagsLoaded };
};

const useTaxonomyListData = () => {
const taxonomyListData = useTaxonomyListDataResponse(org);
const isTaxonomyListLoaded = useIsTaxonomyListDataLoaded(org);
return { taxonomyListData, isTaxonomyListLoaded };
};

const { contentData, isContentDataLoaded } = useContentData();
const { contentTaxonomyTagsData, isContentTaxonomyTagsLoaded } = useContentTaxonomyTagsData();
const { data: contentData, isSuccess: isContentDataLoaded } = useContentData(contentId);
const {
data: contentTaxonomyTagsData,
isSuccess: isContentTaxonomyTagsLoaded,
} = useContentTaxonomyTagsData(contentId);
const { taxonomyListData, isTaxonomyListLoaded } = useTaxonomyListData();

const closeContentTagsDrawer = () => {
Expand Down
5 changes: 2 additions & 3 deletions src/content-tags-drawer/ContentTagsDropDownSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import PropTypes from 'prop-types';
import messages from './messages';
import './ContentTagsDropDownSelector.scss';

import { useTaxonomyTagsDataResponse, useIsTaxonomyTagsDataLoaded } from './data/apiHooks';
import { useTaxonomyTagsData } from './data/apiHooks';

const ContentTagsDropDownSelector = ({
taxonomyId, level, subTagsUrl, lineage, tagsTree,
Expand Down Expand Up @@ -39,8 +39,7 @@ const ContentTagsDropDownSelector = ({
setDropdownStates({ ...dropdownStates, [i]: !dropdownStates[i] });
};

const taxonomyTagsData = useTaxonomyTagsDataResponse(taxonomyId, fetchUrl);
const isTaxonomyTagsLoaded = useIsTaxonomyTagsDataLoaded(taxonomyId, fetchUrl);
const { data: taxonomyTagsData, isSuccess: isTaxonomyTagsLoaded } = useTaxonomyTagsData(taxonomyId, fetchUrl);

const isImplicit = (tag) => {
// Traverse the tags tree using the lineage
Expand Down
2 changes: 1 addition & 1 deletion src/content-tags-drawer/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export async function getContentData(contentId) {
/**
* Update content object's applied tags
* @param {string} contentId The id of the content object (unit/component)
* @param {string} taxonomyId The id of the taxonomy the tags belong to
* @param {number} taxonomyId The id of the taxonomy the tags belong to
* @param {string[]} tags The list of tags (values) to set on content object
* @returns {Promise<Object>}
*/
Expand Down
90 changes: 12 additions & 78 deletions src/content-tags-drawer/data/apiHooks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,117 +9,51 @@ import {

/**
* Builds the query to get the taxonomy tags
* @param {string} taxonomyId The id of the taxonomy to fetch tags for
* @param {number} taxonomyId The id of the taxonomy to fetch tags for
* @param {string} fullPathProvided Optional param that contains the full URL to fetch data
* If provided, we use it instead of generating the URL. This is usually for fetching subTags
* @returns {import("./types.mjs").UseQueryResult}
* @returns {import("@tanstack/react-query").UseQueryResult<import("./types.mjs").TaxonomyTagsData>}
*/
const useTaxonomyTagsData = (taxonomyId, fullPathProvided) => (
export const useTaxonomyTagsData = (taxonomyId, fullPathProvided) => (
useQuery({
queryKey: [`taxonomyTags${ fullPathProvided || taxonomyId }`],
queryFn: () => getTaxonomyTagsData(taxonomyId, fullPathProvided),
})
);

/**
* Gets the taxonomy tags data
* @param {string} taxonomyId The id of the taxonomy to fetch tags for
* @param {string} fullPathProvided Optional param that contains the full URL to fetch data
* If provided, we use it instead of generating the URL. This is usually for fetching subTags
* @returns {import("./types.mjs").TaxonomyTagsData | undefined}
*/
export const useTaxonomyTagsDataResponse = (taxonomyId, fullPathProvided) => {
const response = useTaxonomyTagsData(taxonomyId, fullPathProvided);
if (response.status === 'success') {
return response.data;
}
return undefined;
};

/**
* Returns the status of the taxonomy tags query
* @param {string} taxonomyId The id of the taxonomy to fetch tags for
* @param {string} fullPathProvided Optional param that contains the full URL to fetch data
* If provided, we use it instead of generating the URL. This is usually for fetching subTags
* @returns {boolean}
*/
export const useIsTaxonomyTagsDataLoaded = (taxonomyId, fullPathProvided) => (
useTaxonomyTagsData(taxonomyId, fullPathProvided).status === 'success'
);

/**
* Builds the query to get the taxonomy tags applied to the content object
* @param {string} contentId The id of the content object to fetch the applied tags for
* @returns {import("./types.mjs").UseQueryResult}
* @returns {import("@tanstack/react-query").UseQueryResult<import("./types.mjs").ContentTaxonomyTagsData>}
*/
const useContentTaxonomyTagsData = (contentId) => (
export const useContentTaxonomyTagsData = (contentId) => (
useQuery({
queryKey: ['contentTaxonomyTags', contentId],
queryFn: () => getContentTaxonomyTagsData(contentId),
})
);

/**
* Gets the taxonomy tags applied to the content object
* @param {string} contentId The id of the content object to fetch the applied tags for
* @returns {import("./types.mjs").ContentTaxonomyTagsData | undefined}
*/
export const useContentTaxonomyTagsDataResponse = (contentId) => {
const response = useContentTaxonomyTagsData(contentId);
if (response.status === 'success') {
return response.data;
}
return undefined;
};

/**
* Gets the status of the content taxonomy tags query
* @param {string} contentId The id of the content object to fetch the applied tags for
* @returns {boolean}
*/
export const useIsContentTaxonomyTagsDataLoaded = (contentId) => (
useContentTaxonomyTagsData(contentId).status === 'success'
);

/**
* Builds the query to get meta data about the content object
* @param {string} contentId The id of the content object (unit/component)
* @returns {import("./types.mjs").UseQueryResult}
* @returns {import("@tanstack/react-query").UseQueryResult<import("./types.mjs").ContentData>}
*/
const useContentData = (contentId) => (
export const useContentData = (contentId) => (
useQuery({
queryKey: ['contentData', contentId],
queryFn: () => getContentData(contentId),
})
);

/**
* Gets the information about the content object
* @param {string} contentId The id of the content object (unit/component)
* @returns {import("./types.mjs").ContentData | undefined}
*/
export const useContentDataResponse = (contentId) => {
const response = useContentData(contentId);
if (response.status === 'success') {
return response.data;
}
return undefined;
};

/**
* Gets the status of the content data query
* @param {string} contentId The id of the content object (unit/component)
* @returns {boolean}
*/
export const useIsContentDataLoaded = (contentId) => (
useContentData(contentId).status === 'success'
);

/**
* Builds the mutation to update the tags applied to the content object
* @returns {import("./types.mjs").UseMutationResult}
*/
export const useContentTaxonomyTagsMutation = () => (
/**
* @type {
* import("@tanstack/react-query").MutateFunction<any, any, {contentId: string, taxonomyId: number, tags: string[]}>
* }
*/
useMutation({
mutationFn: ({ contentId, taxonomyId, tags }) => updateContentTaxonomyTags(contentId, taxonomyId, tags),
})
Expand Down
138 changes: 56 additions & 82 deletions src/content-tags-drawer/data/apiHooks.test.jsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,95 @@
import { useQuery } from '@tanstack/react-query';
import { useQuery, useMutation } from '@tanstack/react-query';
import { act } from '@testing-library/react';
import {
useTaxonomyTagsDataResponse,
useIsTaxonomyTagsDataLoaded,
useContentTaxonomyTagsDataResponse,
useIsContentTaxonomyTagsDataLoaded,
useContentDataResponse,
useIsContentDataLoaded,
useTaxonomyTagsData,
useContentTaxonomyTagsData,
useContentData,
useContentTaxonomyTagsMutation,
} from './apiHooks';

import { updateContentTaxonomyTags } from './api';

jest.mock('@tanstack/react-query', () => ({
useQuery: jest.fn(),
useMutation: jest.fn(),
}));

describe('useTaxonomyTagsDataResponse', () => {
it('should return data when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success', data: { data: 'data' } });
const taxonomyId = '123';
const result = useTaxonomyTagsDataResponse(taxonomyId);

expect(result).toEqual({ data: 'data' });
});

it('should return undefined when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
const taxonomyId = '123';
const result = useTaxonomyTagsDataResponse(taxonomyId);

expect(result).toBeUndefined();
});
});
jest.mock('./api', () => ({
updateContentTaxonomyTags: jest.fn(),
}));

describe('useIsTaxonomyTagsDataLoaded', () => {
it('should return true when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success' });
const taxonomyId = '123';
const result = useIsTaxonomyTagsDataLoaded(taxonomyId);
describe('useTaxonomyTagsData', () => {
it('should return success response', () => {
useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' });
const taxonomyId = 123;
const result = useTaxonomyTagsData(taxonomyId);

expect(result).toBe(true);
expect(result).toEqual({ isSuccess: true, data: 'data' });
});

it('should return false when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
const taxonomyId = '123';
const result = useIsTaxonomyTagsDataLoaded(taxonomyId);
it('should return failure response', () => {
useQuery.mockReturnValueOnce({ isSuccess: false });
const taxonomyId = 123;
const result = useTaxonomyTagsData(taxonomyId);

expect(result).toBe(false);
expect(result).toEqual({ isSuccess: false });
});
});

describe('useContentTaxonomyTagsDataResponse', () => {
it('should return data when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success', data: { data: 'data' } });
describe('useContentTaxonomyTagsData', () => {
it('should return success response', () => {
useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' });
const contentId = '123';
const result = useContentTaxonomyTagsDataResponse(contentId);
const result = useContentTaxonomyTagsData(contentId);

expect(result).toEqual({ data: 'data' });
expect(result).toEqual({ isSuccess: true, data: 'data' });
});

it('should return undefined when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
it('should return failure response', () => {
useQuery.mockReturnValueOnce({ isSuccess: false });
const contentId = '123';
const result = useContentTaxonomyTagsDataResponse(contentId);
const result = useContentTaxonomyTagsData(contentId);

expect(result).toBeUndefined();
expect(result).toEqual({ isSuccess: false });
});
});

describe('useIsContentTaxonomyTagsDataLoaded', () => {
it('should return true when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success' });
describe('useContentData', () => {
it('should return success response', () => {
useQuery.mockReturnValueOnce({ isSuccess: true, data: 'data' });
const contentId = '123';
const result = useIsContentTaxonomyTagsDataLoaded(contentId);
const result = useContentData(contentId);

expect(result).toBe(true);
expect(result).toEqual({ isSuccess: true, data: 'data' });
});

it('should return false when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
it('should return failure response', () => {
useQuery.mockReturnValueOnce({ isSuccess: false });
const contentId = '123';
const result = useIsContentTaxonomyTagsDataLoaded(contentId);
const result = useContentData(contentId);

expect(result).toBe(false);
expect(result).toEqual({ isSuccess: false });
});
});

describe('useContentDataResponse', () => {
it('should return data when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success', data: { data: 'data' } });
const contentId = '123';
const result = useContentDataResponse(contentId);
describe('useContentTaxonomyTagsMutation', () => {
it('should call the update content taxonomy tags function', async () => {
useMutation.mockReturnValueOnce({ mutate: jest.fn() });

expect(result).toEqual({ data: 'data' });
});
const mutation = useContentTaxonomyTagsMutation();
mutation.mutate();

it('should return undefined when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
const contentId = '123';
const result = useContentDataResponse(contentId);
expect(useMutation).toBeCalled();

expect(result).toBeUndefined();
});
});

describe('useIsContentDataLoaded', () => {
it('should return true when status is success', () => {
useQuery.mockReturnValueOnce({ status: 'success' });
const contentId = '123';
const result = useIsContentDataLoaded(contentId);

expect(result).toBe(true);
});

it('should return false when status is not success', () => {
useQuery.mockReturnValueOnce({ status: 'error' });
const contentId = '123';
const result = useIsContentDataLoaded(contentId);
const [config] = useMutation.mock.calls[0];
const { mutationFn } = config;

expect(result).toBe(false);
await act(async () => {
const contentId = 'testerContent';
const taxonomyId = 123;
const tags = ['tag1', 'tag2'];
await mutationFn({ contentId, taxonomyId, tags });
expect(updateContentTaxonomyTags).toBeCalledWith(contentId, taxonomyId, tags);
});
});
});
5 changes: 0 additions & 5 deletions src/content-tags-drawer/data/types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,3 @@
* @property {Object} data
* @property {string} status
*/

/**
* @typedef {Object} UseMutationResult
* @property {(variables: {Object}, { onSuccess, onSettled, onError }) => void} mutate
*/

0 comments on commit bdc8d30

Please sign in to comment.