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.
This commit is contained in:
parent
0c2c21b67e
commit
4b0318cd38
4 changed files with 31 additions and 11 deletions
|
@ -1,10 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectTagsForUri } from 'redux/selectors/claims';
|
import { selectTagsForUri } from 'redux/selectors/claims';
|
||||||
import { selectFollowedTags } from 'redux/selectors/tags';
|
import { selectFollowedTags } from 'redux/selectors/tags';
|
||||||
import ClaimTags from './view';
|
import ClaimTags from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
tags: makeSelectTagsForUri(props.uri)(state),
|
tags: selectTagsForUri(state, props.uri),
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -65,11 +65,22 @@ export default handleActions(
|
||||||
[ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array<string> } }) => {
|
[ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array<string> } }) => {
|
||||||
const { tags } = action.data;
|
const { tags } = action.data;
|
||||||
if (Array.isArray(tags)) {
|
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 {
|
return {
|
||||||
...state,
|
...state,
|
||||||
followedTags: tags,
|
followedTags: next || [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purge 'followedTags'
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
};
|
};
|
||||||
|
|
|
@ -270,6 +270,11 @@ export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
|
||||||
return byChannel['pageCount'];
|
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) =>
|
export const makeSelectMetadataForUri = (uri: string) =>
|
||||||
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
||||||
const metadata = claim && claim.value;
|
const metadata = claim && claim.value;
|
||||||
|
@ -537,6 +542,10 @@ export const makeSelectMyChannelPermUrlForName = (name: string) =>
|
||||||
return matchingClaim ? matchingClaim.permanent_url : null;
|
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) =>
|
export const makeSelectTagsForUri = (uri: string) =>
|
||||||
createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => {
|
createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => {
|
||||||
return (metadata && metadata.tags) || [];
|
return (metadata && metadata.tags) || [];
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { createSelector } from 'reselect';
|
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 selectKnownTagsByName = createSelector(selectState, (state: TagState): KnownTags => state.knownTags);
|
||||||
|
|
||||||
export const selectFollowedTagsList = createSelector(selectState, (state: TagState): Array<string> =>
|
export const selectFollowedTagsList = (state: State) => selectState(state).followedTags;
|
||||||
state.followedTags.filter(tag => typeof tag === 'string')
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array<string>): Array<Tag> =>
|
export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array<string>): Array<Tag> =>
|
||||||
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(
|
export const selectUnfollowedTags = createSelector(
|
||||||
|
@ -19,7 +19,7 @@ export const selectUnfollowedTags = createSelector(
|
||||||
(tagsByName: KnownTags, followedTags: Array<string>): Array<Tag> => {
|
(tagsByName: KnownTags, followedTags: Array<string>): Array<Tag> => {
|
||||||
const followedTagsSet = new Set(followedTags);
|
const followedTagsSet = new Set(followedTags);
|
||||||
let tagsToReturn = [];
|
let tagsToReturn = [];
|
||||||
Object.keys(tagsByName).forEach(key => {
|
Object.keys(tagsByName).forEach((key) => {
|
||||||
if (!followedTagsSet.has(key)) {
|
if (!followedTagsSet.has(key)) {
|
||||||
const { name } = tagsByName[key];
|
const { name } = tagsByName[key];
|
||||||
tagsToReturn.push({ name: name.toLowerCase() });
|
tagsToReturn.push({ name: name.toLowerCase() });
|
||||||
|
@ -31,6 +31,6 @@ export const selectUnfollowedTags = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const makeSelectIsFollowingTag = (tag: string) =>
|
export const makeSelectIsFollowingTag = (tag: string) =>
|
||||||
createSelector(selectFollowedTags, followedTags => {
|
createSelector(selectFollowedTags, (followedTags) => {
|
||||||
return followedTags.some(followedTag => followedTag.name === tag.toLowerCase());
|
return followedTags.some((followedTag) => followedTag.name === tag.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue