mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 08:31:23 -05:00
Merge pull request #5231 from paulkaplan/use-getstate-in-thunks
Use getState in thunks instead of passing props around
This commit is contained in:
commit
7a8f2b61dc
6 changed files with 78 additions and 53 deletions
|
@ -186,3 +186,6 @@ module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
|
|||
module.exports.resetComments = () => ({
|
||||
type: 'RESET_COMMENTS'
|
||||
});
|
||||
|
||||
// Selectors
|
||||
module.exports.selectCommentCount = state => state.comments.comments.length;
|
||||
|
|
|
@ -18,9 +18,19 @@ const {
|
|||
setError,
|
||||
setReplies,
|
||||
setRepliesDeleted,
|
||||
setRepliesRestored
|
||||
setRepliesRestored,
|
||||
selectCommentCount
|
||||
} = require('../redux/comments.js');
|
||||
|
||||
const {
|
||||
selectIsAdmin,
|
||||
selectToken
|
||||
} = require('./session');
|
||||
|
||||
const {
|
||||
selectStudioId
|
||||
} = require('./studio');
|
||||
|
||||
const getReplies = (studioId, commentIds, offset, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('replies', Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
|
@ -50,8 +60,13 @@ const getReplies = (studioId, commentIds, offset, isAdmin, token) => (dispatch =
|
|||
});
|
||||
});
|
||||
|
||||
const getTopLevelComments = (id, offset, isAdmin, token) => (dispatch => {
|
||||
const getTopLevelComments = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
const state = getState();
|
||||
const id = selectStudioId(state);
|
||||
const offset = selectCommentCount(state);
|
||||
const isAdmin = selectIsAdmin(state);
|
||||
const token = selectToken(state);
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : ''}/studios/${id}/comments`,
|
||||
authentication: token ? token : null,
|
||||
|
|
|
@ -3,7 +3,7 @@ const keyMirror = require('keymirror');
|
|||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const {selectUserId, selectIsAdmin, selectIsSocial} = require('./session');
|
||||
const {selectUserId, selectIsAdmin, selectIsSocial, selectUsername, selectToken} = require('./session');
|
||||
|
||||
const Status = keyMirror({
|
||||
FETCHED: null,
|
||||
|
@ -77,10 +77,31 @@ const setRoles = roles => ({
|
|||
roles: roles
|
||||
});
|
||||
|
||||
// Thunks
|
||||
// Selectors
|
||||
|
||||
const getInfo = studioId => (dispatch => {
|
||||
// Fine-grain selector helpers - not exported, use the higher level selectors below
|
||||
const isCreator = state => selectUserId(state) === state.studio.owner;
|
||||
const isCurator = state => state.studio.curator;
|
||||
const isManager = state => state.studio.manager || isCreator(state);
|
||||
|
||||
// Action-based permissions selectors
|
||||
const selectCanEditInfo = state => selectIsAdmin(state) || isManager(state);
|
||||
const selectCanAddProjects = state =>
|
||||
isManager(state) ||
|
||||
isCurator(state) ||
|
||||
(selectIsSocial(state) && state.studio.openToAll);
|
||||
|
||||
// This isn't "canComment" since they could be muted, but comment composer handles that
|
||||
const selectShowCommentComposer = state => selectIsSocial(state);
|
||||
|
||||
// Data selectors
|
||||
const selectStudioId = state => state.studio.id;
|
||||
|
||||
|
||||
// Thunks
|
||||
const getInfo = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('infoStatus', Status.FETCHING));
|
||||
const studioId = selectStudioId(getState());
|
||||
api({uri: `/studios/${studioId}`}, (err, body, res) => {
|
||||
if (err || typeof body === 'undefined' || res.statusCode !== 200) {
|
||||
dispatch(setFetchStatus('infoStatus', Status.ERROR, err));
|
||||
|
@ -99,8 +120,12 @@ const getInfo = studioId => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
const getRoles = (studioId, username, token) => (dispatch => {
|
||||
const getRoles = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('rolesStatus', Status.FETCHING));
|
||||
const state = getState();
|
||||
const studioId = selectStudioId(state);
|
||||
const username = selectUsername(state);
|
||||
const token = selectToken(state);
|
||||
api({
|
||||
uri: `/studios/${studioId}/users/${username}`,
|
||||
authentication: token
|
||||
|
@ -119,23 +144,6 @@ const getRoles = (studioId, username, token) => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
// Selectors
|
||||
|
||||
// Fine-grain selector helpers - not exported, use the higher level selectors below
|
||||
const isCreator = state => selectUserId(state) === state.studio.owner;
|
||||
const isCurator = state => state.studio.curator;
|
||||
const isManager = state => state.studio.manager || isCreator(state);
|
||||
|
||||
// Action-based permissions selectors
|
||||
const selectCanEditInfo = state => selectIsAdmin(state) || isManager(state);
|
||||
const selectCanAddProjects = state =>
|
||||
isManager(state) ||
|
||||
isCurator(state) ||
|
||||
(selectIsSocial(state) && state.studio.openToAll);
|
||||
|
||||
// This isn't "canComment" since they could be muted, but comment composer handles that
|
||||
const selectShowCommentComposer = state => selectIsSocial(state);
|
||||
|
||||
module.exports = {
|
||||
getInitialState,
|
||||
studioReducer,
|
||||
|
@ -146,6 +154,7 @@ module.exports = {
|
|||
getRoles,
|
||||
|
||||
// Selectors
|
||||
selectStudioId,
|
||||
selectCanEditInfo,
|
||||
selectCanAddProjects,
|
||||
selectShowCommentComposer
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useEffect, useCallback} from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
@ -13,7 +13,7 @@ import {selectShowCommentComposer} from '../../redux/studio.js';
|
|||
|
||||
const StudioComments = ({
|
||||
comments,
|
||||
getTopLevelComments,
|
||||
handleLoadMoreComments,
|
||||
handleNewComment,
|
||||
moreCommentsToLoad,
|
||||
replies,
|
||||
|
@ -21,13 +21,9 @@ const StudioComments = ({
|
|||
}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
const handleLoadComments = useCallback(() => {
|
||||
getTopLevelComments(studioId, comments.length);
|
||||
}, [studioId, comments.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (comments.length === 0) getTopLevelComments(studioId, 0);
|
||||
}, [studioId]);
|
||||
if (comments.length === 0) handleLoadMoreComments();
|
||||
}, []); // Only runs once after the first render
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -58,7 +54,7 @@ const StudioComments = ({
|
|||
{moreCommentsToLoad &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
onClick={handleLoadComments}
|
||||
onClick={handleLoadMoreComments}
|
||||
>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</Button>
|
||||
|
@ -70,7 +66,7 @@ const StudioComments = ({
|
|||
|
||||
StudioComments.propTypes = {
|
||||
comments: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
getTopLevelComments: PropTypes.func,
|
||||
handleLoadMoreComments: PropTypes.func,
|
||||
handleNewComment: PropTypes.func,
|
||||
moreCommentsToLoad: PropTypes.bool,
|
||||
replies: PropTypes.shape({}),
|
||||
|
@ -86,7 +82,7 @@ export default connect(
|
|||
shouldShowCommentComposer: selectShowCommentComposer(state)
|
||||
}),
|
||||
{
|
||||
getTopLevelComments: studioCommentActions.getTopLevelComments,
|
||||
handleLoadMoreComments: studioCommentActions.getTopLevelComments,
|
||||
handleNewComment: studioCommentActions.addNewComment
|
||||
}
|
||||
)(StudioComments);
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {connect} from 'react-redux';
|
||||
import Debug from './debug.jsx';
|
||||
|
||||
import {selectUsername, selectToken} from '../../redux/session';
|
||||
import {selectIsLoggedIn} from '../../redux/session';
|
||||
import {getInfo, getRoles, selectCanEditInfo} from '../../redux/studio';
|
||||
|
||||
const StudioInfo = ({username, studio, token, canEditInfo, onLoadInfo, onLoadRoles}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
const StudioInfo = ({isLoggedIn, studio, canEditInfo, onLoadInfo, onLoadRoles}) => {
|
||||
useEffect(() => { // Load studio info after first render
|
||||
if (studioId) onLoadInfo(studioId);
|
||||
}, [studioId]);
|
||||
onLoadInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() => { // Load roles info once the username is available
|
||||
if (studioId && username && token) onLoadRoles(studioId, username, token);
|
||||
}, [studioId, username, token]);
|
||||
useEffect(() => { // Load roles info once the user is logged in is available
|
||||
if (isLoggedIn) onLoadRoles();
|
||||
}, [isLoggedIn]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -35,8 +32,7 @@ const StudioInfo = ({username, studio, token, canEditInfo, onLoadInfo, onLoadRol
|
|||
|
||||
StudioInfo.propTypes = {
|
||||
canEditInfo: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
studio: PropTypes.shape({
|
||||
// Fill this in as the data is used, just for demo now
|
||||
}),
|
||||
|
@ -47,13 +43,11 @@ StudioInfo.propTypes = {
|
|||
export default connect(
|
||||
state => ({
|
||||
studio: state.studio,
|
||||
username: selectUsername(state),
|
||||
token: selectToken(state),
|
||||
isLoggedIn: selectIsLoggedIn(state),
|
||||
canEditInfo: selectCanEditInfo(state)
|
||||
}),
|
||||
dispatch => ({
|
||||
onLoadInfo: studioId => dispatch(getInfo(studioId)),
|
||||
onLoadRoles: (studioId, username, token) => dispatch(
|
||||
getRoles(studioId, username, token))
|
||||
})
|
||||
{
|
||||
onLoadInfo: getInfo,
|
||||
onLoadRoles: getRoles
|
||||
}
|
||||
)(StudioInfo);
|
||||
|
|
|
@ -78,5 +78,13 @@ render(
|
|||
[activity.key]: activity.reducer,
|
||||
studio: studioReducer,
|
||||
comments: commentsReducer
|
||||
},
|
||||
{
|
||||
studio: {
|
||||
// Include the studio id in the initial state to allow us
|
||||
// to stop passing around the studio id in components
|
||||
// when it is only needed for data fetching, not for rendering.
|
||||
id: window.location.pathname.split('/')[2]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue