Skip to content

Commit

Permalink
Refactor useFetch to return hasMore, loadingMore and count
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexVelezLl committed Dec 16, 2024
1 parent d9b10b6 commit 6471f0b
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 82 deletions.
79 changes: 30 additions & 49 deletions kolibri/plugins/coach/assets/src/composables/useFetch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import get from 'lodash/get';
import { ref, computed } from 'vue';
import { ViewMoreButtonStates } from '../constants';

/**
* A composable for managing fetch operations with optional methods for additional data fetching.
Expand Down Expand Up @@ -43,7 +42,7 @@ import { ViewMoreButtonStates } from '../constants';
* ```js
* // Suppose the response object looks like this:
* const response = {
* payload: [...],
* results: [...],
* more: { page: 2 }
* };
*
Expand All @@ -52,6 +51,22 @@ import { ViewMoreButtonStates } from '../constants';
* useFetch({ moreKey: "more" });
* ```
*
* @param {string} [options.countKey] The key in the response object where the count of
* the data is located. Count is the total number of items.
* * You can use `lodash.get`-compatible keys to access nested objects (e.g., "data.count").
*
* Example:
* ```js
* // Suppose the response object looks like this:
* const response = {
* results: [...],
* count: 100
* };
*
* // By specifying `countKey`, you tell the composable where to find the count of the data:
* const { count } = useFetch({ countKey: "count" });
* console.log(count); // Outputs: 100
* ```
*
* @param {(more, ...args) => Promise<any>} [options.fetchMoreMethod] Function to fetch more data.
* * This function receives a "more" object as its first argument. This "more" object is specified
Expand All @@ -68,57 +83,31 @@ import { ViewMoreButtonStates } from '../constants';
* ```
*
*
* @param {Object.<string, string>} [options.additionalDataKeys] An object that maps additional
* data keys for the response object.
* * In the `{ key: value }` pair:
* - The `key` will be used as the property name in the returned `additionalData` object.
* - The `value` specifies the key in the response object from which the data will be retrieved.
*
* Example:
* ```js
* const additionalDataKeys = {
* userId: "user_id", // The `userId` property in `additionalData` will map to `response.user_id`
* userName: "name" // The `userName` property in `additionalData` will map to `response.name`
* };
*
* const { additionalData } = useFetch({ additionalDataKeys });
* console.log(additionalData.userId); // Outputs the value of `response.user_id`
* console.log(additionalData.userName); // Outputs the value of `response.name`
* ```
*
*
* @typedef {Object} FetchObject
* @property {any} data The main fetched data.
* @property {Object} error Error object if a fetch data failed.
* @property {any} count The count of the fetched data. E.g., the total number of items.
* @property {boolean} loading Data loading state. This loading doesnt reflect the loading when
* fetching more data. refer to moreState for that.
* @property {string} moreState State of the fetch more data, it could be LOADING, HAS_MORE,
* NO_MORE or ERROR.
* @property {Object<string, any>} additionalData Extra data specified by `additionalDataKeys`.
* fetching more data. refer to `loadingMore` for that.
* @property {boolean} loadingMore Loading state when fetching more data. This is different from
* `loading` which is for the main data fetch.
* @property {boolean} hasMore A computed property to check if there is more data to fetch.
* @property {(...args) => Promise<void>} fetchData A method to manually trigger the main fetch.
* @property {(...args) => Promise<void>} fetchMore A method to manually trigger fetch more data.
*
* @returns {FetchObject} An object with properties and methods for managing the fetch process.
*/
export default function useFetch(options) {
const { fetchMethod, fetchMoreMethod, dataKey, moreKey, additionalDataKeys } = options || {};
const { fetchMethod, fetchMoreMethod, dataKey, moreKey, countKey } = options || {};

const loading = ref(false);
const data = ref(null);
const error = ref(null);
const more = ref(null);
const count = ref(null);
const loadingMore = ref(false);
const additionalData = ref(null);

const moreState = computed(() => {
if (loadingMore.value) {
return ViewMoreButtonStates.LOADING;
}
if (more.value) {
return ViewMoreButtonStates.HAS_MORE;
}
return ViewMoreButtonStates.NO_MORE;
});
const hasMore = computed(() => more.value != null);

const _setFromKeys = (response, loadingMore) => {
let responseData;
Expand All @@ -141,17 +130,8 @@ export default function useFetch(options) {
more.value = get(response, moreKey) || null;
}

if (additionalDataKeys) {
const newAdditionalData = {};
// The `key` will be used as the property name in the returned `additionalData` object.
// The `value` specifies the key in the response object from which the data will be get.
for (const [key, value] of Object.entries(additionalDataKeys)) {
// if value is an empty string, that means that no specific data is required,
// but the whole response object.
newAdditionalData[key] = value === '' ? response : get(response, value);
}

additionalData.value = newAdditionalData;
if (countKey) {
count.value = get(response, countKey) || null;
}
};

Expand Down Expand Up @@ -190,9 +170,10 @@ export default function useFetch(options) {
return {
data,
error,
count,
loading,
moreState,
additionalData,
hasMore,
loadingMore,
fetchData,
fetchMore,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function useResourceSelection() {

const selectionRules = ref([]);
const selectedResources = ref([]);
const topic = ref(null);

const bookmarksFetch = useFetch({
fetchMethod: () =>
Expand All @@ -57,9 +58,7 @@ export default function useResourceSelection() {
}),
dataKey: 'results',
moreKey: 'more',
additionalDataKeys: {
count: 'count',
},
countKey: 'count',
});

const channelsFetch = useFetch({
Expand All @@ -71,24 +70,16 @@ export default function useResourceSelection() {
}),
});

const treeFetch = useFetch({
fetchMethod: () =>
ContentNodeResource.fetchTree({ id: topicId.value, params: { include_coach_content: true } }),
fetchMoreMethod: more => ContentNodeResource.fetchTree({ id: topicId.value, params: more }),
dataKey: 'children.results',
moreKey: 'children.more.params',
additionalDataKeys: {
topic: '', // return the whole response as topic
},
});
const fetchTree = async (params = {}) => {
topic.value = await ContentNodeResource.fetchTree(params);
return topic.value.children;
};

const topic = computed(() => {
if (topicId.value) {
const { additionalData } = treeFetch;
const { topic } = additionalData.value;
return topic;
}
return null;
const treeFetch = useFetch({
fetchMethod: () => fetchTree({ id: topicId.value, params: { include_coach_content: true } }),
fetchMoreMethod: more => fetchTree(more),
dataKey: 'results',
moreKey: 'more',
});

watch(topicId, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import { ContentNodeKinds } from 'kolibri/constants';
import ContentCardList from '../../lessons/LessonResourceSelectionPage/ContentCardList.vue';
import ResourceSelectionBreadcrumbs from '../../lessons/LessonResourceSelectionPage/SearchTools/ResourceSelectionBreadcrumbs.vue';
import { PageNames } from '../../../constants';
import { PageNames, ViewMoreButtonStates } from '../../../constants';
export default {
name: 'UpdatedResourceSelection',
Expand All @@ -62,16 +62,19 @@
type: Array,
required: true,
},
viewMoreButtonState: {
type: String,
required: false,
default: null,
hasMore: {
type: Boolean,
default: false,
},
fetchMore: {
type: Function,
required: false,
default: null,
},
loadingMore: {
type: Boolean,
default: false,
},
selectionRules: {
type: Array,
required: false,
Expand Down Expand Up @@ -109,6 +112,15 @@
showSelectAll() {
return this.canSelectAll && this.multi && this.selectableContentList.length > 0;
},
viewMoreButtonState() {
if (this.loadingMore) {
return ViewMoreButtonStates.LOADING;
}
if (this.hasMore) {
return ViewMoreButtonStates.HAS_MORE;
}
return ViewMoreButtonStates.NO_MORE;
},
},
methods: {
contentLink(content) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
<UpdatedResourceSelection
canSelectAll
:contentList="contentList"
:viewMoreButtonState="viewMoreButtonState"
:hasMore="hasMore"
:fetchMore="fetchMore"
:loadingMore="loadingMore"
:selectionRules="selectionRules"
:selectedResources="selectedResources"
@selectResources="$emit('selectResources', $event)"
Expand Down Expand Up @@ -43,12 +44,13 @@
});
});
const { data, moreState, fetchMore } = props.bookmarksFetch;
const { data, hasMore, fetchMore, loadingMore } = props.bookmarksFetch;
return {
contentList: data,
viewMoreButtonState: moreState,
hasMore,
fetchMore,
loadingMore,
};
},
props: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
canSelectAll
:topic="topic"
:contentList="contentList"
:viewMoreButtonState="viewMoreButtonState"
:hasMore="hasMore"
:fetchMore="fetchMore"
:loadingMore="loadingMore"
:selectionRules="selectionRules"
:selectedResources="selectedResources"
@selectResources="$emit('selectResources', $event)"
Expand Down Expand Up @@ -71,11 +72,12 @@
});
});
const { data, moreState, fetchMore } = props.treeFetch;
const { data, hasMore, fetchMore, loadingMore } = props.treeFetch;
return {
contentList: data,
viewMoreButtonState: moreState,
hasMore,
fetchMore,
loadingMore,
searchLabel$,
selectFromChannels$,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@
},
setup(props) {
const { bookmarksFetch, channelsFetch } = props;
const { additionalData } = bookmarksFetch;
const { count: bookmarksCount } = additionalData.value;
const { count: bookmarksCount } = bookmarksFetch;
const { data: channels } = channelsFetch;
Expand Down

0 comments on commit 6471f0b

Please sign in to comment.