From 29b845c3fc6e98081df7b55e0db8ab950b90bfa8 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 8 Oct 2021 13:52:30 +0800 Subject: [PATCH] Optimize tags and followedTags followedTags: - Moved the filtering to the reducer side, so that we don't do it every time. We can't rely on `createSelector` because the store will be invalidated on each `USER_STATE_POPULATE`, unfortunately. tags: - Memoize via re-reselect for the "ForUri" selector. --- ui/component/claimTags/index.js | 4 ++-- ui/redux/reducers/tags.js | 13 ++++++++++++- ui/redux/selectors/claims.js | 9 +++++++++ ui/redux/selectors/tags.js | 16 ++++++++-------- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/ui/component/claimTags/index.js b/ui/component/claimTags/index.js index a0280f138..385aca981 100644 --- a/ui/component/claimTags/index.js +++ b/ui/component/claimTags/index.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; -import { makeSelectTagsForUri } from 'redux/selectors/claims'; +import { selectTagsForUri } from 'redux/selectors/claims'; import { selectFollowedTags } from 'redux/selectors/tags'; import ClaimTags from './view'; const select = (state, props) => ({ - tags: makeSelectTagsForUri(props.uri)(state), + tags: selectTagsForUri(state, props.uri), followedTags: selectFollowedTags(state), }); diff --git a/ui/redux/reducers/tags.js b/ui/redux/reducers/tags.js index 4edd4fbf7..c3be1baf2 100644 --- a/ui/redux/reducers/tags.js +++ b/ui/redux/reducers/tags.js @@ -65,11 +65,22 @@ export default handleActions( [ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array } }) => { const { tags } = action.data; if (Array.isArray(tags)) { + const next = tags && tags.filter((tag) => typeof tag === 'string'); + const prev = state.followedTags; + + if (next && prev && prev.length === next.length && prev.every((value, index) => value === next[index])) { + // No changes + return state; + } + + // New state return { ...state, - followedTags: tags, + followedTags: next || [], }; } + + // Purge 'followedTags' return { ...state, }; diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index 06f26fd1f..45f12936e 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -267,6 +267,11 @@ export const makeSelectTotalPagesInChannelSearch = (uri: string) => return byChannel['pageCount']; }); +export const selectMetadataForUri = createCachedSelector(selectClaimForUri, (claim, uri) => { + const metadata = claim && claim.value; + return metadata || (claim === undefined ? undefined : null); +})((state, uri) => uri); + export const makeSelectMetadataForUri = (uri: string) => createSelector(makeSelectClaimForUri(uri), (claim) => { const metadata = claim && claim.value; @@ -534,6 +539,10 @@ export const makeSelectMyChannelPermUrlForName = (name: string) => return matchingClaim ? matchingClaim.permanent_url : null; }); +export const selectTagsForUri = createCachedSelector(selectMetadataForUri, (metadata: ?GenericMetadata) => { + return (metadata && metadata.tags) || []; +})((state, uri) => uri); + export const makeSelectTagsForUri = (uri: string) => createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => { return (metadata && metadata.tags) || []; diff --git a/ui/redux/selectors/tags.js b/ui/redux/selectors/tags.js index c75fbaa39..1651f14d9 100644 --- a/ui/redux/selectors/tags.js +++ b/ui/redux/selectors/tags.js @@ -1,16 +1,16 @@ // @flow import { createSelector } from 'reselect'; -const selectState = (state: { tags: TagState }) => state.tags || {}; +type State = { tags: TagState }; + +const selectState = (state: State) => state.tags || {}; export const selectKnownTagsByName = createSelector(selectState, (state: TagState): KnownTags => state.knownTags); -export const selectFollowedTagsList = createSelector(selectState, (state: TagState): Array => - state.followedTags.filter(tag => typeof tag === 'string') -); +export const selectFollowedTagsList = (state: State) => selectState(state).followedTags; export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array): Array => - followedTags.map(tag => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name)) + followedTags.map((tag) => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name)) ); export const selectUnfollowedTags = createSelector( @@ -19,7 +19,7 @@ export const selectUnfollowedTags = createSelector( (tagsByName: KnownTags, followedTags: Array): Array => { const followedTagsSet = new Set(followedTags); let tagsToReturn = []; - Object.keys(tagsByName).forEach(key => { + Object.keys(tagsByName).forEach((key) => { if (!followedTagsSet.has(key)) { const { name } = tagsByName[key]; tagsToReturn.push({ name: name.toLowerCase() }); @@ -31,6 +31,6 @@ export const selectUnfollowedTags = createSelector( ); export const makeSelectIsFollowingTag = (tag: string) => - createSelector(selectFollowedTags, followedTags => { - return followedTags.some(followedTag => followedTag.name === tag.toLowerCase()); + createSelector(selectFollowedTags, (followedTags) => { + return followedTags.some((followedTag) => followedTag.name === tag.toLowerCase()); });