My app renders a list of videos, clicking on each video fetches and renders a list of comments posted for this video.
I am trying to setup my Redux store to generate a reducer with key based on the video ID and store associated comments as an array. The idea is to cache these IDs and when flipping between the videos, to display what has already been fetched before paginating more results on request.
I am doing this in the COMMENTS REDUCER:
const byVideo = (state = {}, action) => {
const comments = (state = [], action) => {
switch (action.type) {
case GET_COMMENTS_SUCCESS:
return action.comments.map(comment => comment._id);
default:
return state;
}
};
switch (action.type) {
case GET_COMMENTS_SUCCESS:
return {
[action.videoId]: comments(state[action.videoId], action)
};
default:
return state;
}
};
The issue i am having is with mapStateToProps and my selectors. mapStateToProps calculated the data before my action fires off and data is fetched and dynamic key is created in the reducer. As a result, when i try to map over the list of comment IDs associated with the key, i get "Cannot read property 'map' of undefined.
Specifically on:
getVisibleComments
reducers/index.js:33
30 | export const getVisibleComments = (state, videoId) => {
31 | const ids = fromComments.getIds(state.comments, videoId);
32 | console.log(ids);
> 33 | return ids.map(id => fromComments.getComment(state.comment,
id));
34 | };
I have tried to follow the example of Redux Reddit API = https://redux.js.org/advanced/example-reddit-api, where the reducer key is also created dynamically with [action.subreddit].
Is there an alternative solution to generate the dynamic key here or am i doomed with my current approach because mapStateToProps will always compute before component fully mounts and my fetch is dispatched?
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { fetchComments } from '../store/actions/comments';
import { getVisibleComments, getIsCommentsFetching } from
'../store/reducers';
import CommentList from './commentList';
class visibleCommentList extends Component {
componentDidMount() {
const { vimeo_id } = this.props.match.params;
this.props.fetchComments(vimeo_id);
}
render() {
const { isFetching, comments } = this.props;
return <CommentList isFetching={isFetching} data={comments} />;
}
}
const mapStateToProps = (state, ownProps) => {
const vimeo_id = ownProps.match.params.vimeo_id;
return {
comments: getVisibleComments(state, vimeo_id),
isFetching: getIsCommentsFetching(state)
};
};
export default withRouter(
connect(
mapStateToProps,
{ fetchComments }
)(visibleCommentList)
);
// COMMENT REDUCER
import { combineReducers } from 'redux';
import {
GET_COMMENTS_SUCCESS,
GET_COMMENTS_REQUEST,
GET_COMMENTS_FAILURE
} from '../actions/types';
const byIds = (state = {}, action) => {
const { comments } = action;
switch (action.type) {
case GET_COMMENTS_SUCCESS:
const nextState = { ...state };
comments.forEach(comment => (nextState[comment._id] = comment));
return nextState;
default:
return state;
}
};
const byVideo = (state = {}, action) => {
const comments = (state = [], action) => {
switch (action.type) {
case GET_COMMENTS_SUCCESS:
return action.comments.map(comment => comment._id);
default:
return state;
}
};
switch (action.type) {
case GET_COMMENTS_SUCCESS:
return {
[action.videoId]: comments(state[action.videoId], action)
};
default:
return state;
}
};
const isFetching = (state = false, action) => {
switch (action.type) {
case GET_COMMENTS_REQUEST:
return true;
case GET_COMMENTS_SUCCESS:
case GET_COMMENTS_FAILURE:
return false;
default:
return state;
}
};
const comments = combineReducers({
byIds,
isFetching,
byVideo
});
export default comments;
export const getComment = (state, id) => state.byIds[id];
export const getIds = (state, videoId) => state.byVideo[videoId];
export const getIsCommentsFetching = state => state.isFetching;
// ROOT REDUCER
import { combineReducers } from 'redux';
import createVideoIdsList, * as fromList from './createVideoIdsList';
import byIds, * as fromById from './byIds';
import comments, * as fromComments from './commentsByIds';
const listByFilter = combineReducers({
all: createVideoIdsList('all'),
plays: createVideoIdsList('plays'),
likes: createVideoIdsList('likes'),
release_date: createVideoIdsList('release_date'),
comments: createVideoIdsList('comments')
});
const rootReducer = combineReducers({
byIds,
listByFilter,
comments
});
export default rootReducer;
export const getVisibleVideos = (state, filter) => {
const ids = fromList.getIds(state.listByFilter[filter]);
return ids.map(id => fromById.getVideo(state.byIds, id));
};
export const getIsFetching = (state, filter) =>
fromList.getIsFetching(state.listByFilter[filter]);
export const getVisibleComments = (state, videoId) => {
const ids = fromComments.getIds(state.comments, videoId);
console.log(ids);
return ids.map(id => fromComments.getComment(state.comment, id));
};
export const getIsCommentsFetching = state =>
fromComments.getIsCommentsFetching(state.comments);