From 5784fd32a2d4a1d00b3513243b2021323ab5b490 Mon Sep 17 00:00:00 2001 From: Mischa Radenovic Date: Sat, 30 Jan 2021 22:55:31 -0500 Subject: [PATCH] Move logic to redux Pros: - [Strongly recomended](https://redux.js.org/style-guide/style-guide#put-as-much-logic-as-possible-in-reducers) - Easier testing - Easier mocking of libraries and use of storybook - More flexible notification handling --- simplq/src/api/axios-alt.js | 35 +++++++++++++++++++ simplq/src/components/pages/Home/MyQueues.jsx | 18 +++++++++- simplq/src/store/appSlice.js | 12 +++++++ simplq/src/store/index.js | 10 +++++- simplq/src/store/queuesSlice.js | 31 ++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 simplq/src/api/axios-alt.js create mode 100644 simplq/src/store/queuesSlice.js diff --git a/simplq/src/api/axios-alt.js b/simplq/src/api/axios-alt.js new file mode 100644 index 00000000..b269dbba --- /dev/null +++ b/simplq/src/api/axios-alt.js @@ -0,0 +1,35 @@ +import axios from 'axios'; +// TODO: Read base url from env +const baseURL = 'https://devbackend.simplq.me/v1'; + +/** + * Async function for for sending request with Auth0 + * + * It combines usRequest and maqeRequest. Since + * @auth0/auth0-react can work only wthin a component, + * the whole auth object created with useAuth() must be + * passed as parameter. + * + * @param {Object} auth object returned by useAuth() from @auth0/auth0-react. + * @param {Object} payload object used in action creators in redux-toolkit. + */ +const makeAuthedRequest = async (auth, request) => { + // TODO: Is "audience" == "bseURL"? Read from env or use baseURL + const accessToken = auth.isAuthenticated + ? await auth.getAccessTokenSilently({ audience: 'https://devbackend.simplq.me/v1' }) + : 'anonymous'; + + return axios({ + baseURL, + ...request, + headers: { + ...request.headers, + // Add the Authorization header to the existing headers + Authorization: `Bearer ${accessToken}`, + }, + }).then((response) => { + return response.data; + }); +}; + +export default makeAuthedRequest; diff --git a/simplq/src/components/pages/Home/MyQueues.jsx b/simplq/src/components/pages/Home/MyQueues.jsx index 354fc0ca..953816ca 100644 --- a/simplq/src/components/pages/Home/MyQueues.jsx +++ b/simplq/src/components/pages/Home/MyQueues.jsx @@ -3,6 +3,8 @@ import DeleteIcon from '@material-ui/icons/Delete'; import IconButton from '@material-ui/core/IconButton'; import { useHistory } from 'react-router'; import { useAuth0 } from '@auth0/auth0-react'; +import { fetchQueues } from 'store/queuesSlice'; +import { useDispatch, useSelector } from 'react-redux'; import styles from './home.module.scss'; import { QueueRequestFactory } from '../../../api/requestFactory'; import useRequest from '../../../api/useRequest'; @@ -12,12 +14,26 @@ export default () => { const { requestMaker } = useRequest(); const [myQueues, setMyQueues] = useState([]); const { isAuthenticated } = useAuth0(); + const auth = useAuth0(); + const dispatch = useDispatch(); + const queues = useSelector((state) => state.queues); useEffect(() => { if (isAuthenticated) requestMaker(QueueRequestFactory.getMyQueues()).then((resp) => setMyQueues(resp.queues)); }, [requestMaker]); + useEffect(() => { + if (isAuthenticated) { + const arg = { + auth, + // example payload for async thunk + payload: { someKey: 'Some value' }, + }; + dispatch(fetchQueues(arg)); + } + }, []); + if (!isAuthenticated) { return null; } @@ -34,7 +50,7 @@ export default () => { ? "Looks like you don't have any active queues. Start by creating one..." : 'What would you like to do today? Here are your active queues:'}

- {myQueues.map((queue) => { + {queues.map((queue) => { const handler = () => history.push(`/queue/${queue.queueId}`); return (
{ + state.infoText = `Loading queues for ${action.meta.arg.auth.user.name}...`; + }, + [fetchQueues.rejected]: (state, action) => { + state.errorText = action.error.message; + }, + [fetchQueues.fulfilled]: (state, action) => { + state.infoText = `Number of queues fetched: ${action.payload.queues.length}`; + }, + }, }); export const { diff --git a/simplq/src/store/index.js b/simplq/src/store/index.js index 02d6741e..9a032475 100644 --- a/simplq/src/store/index.js +++ b/simplq/src/store/index.js @@ -1,10 +1,18 @@ -import { combineReducers, configureStore } from '@reduxjs/toolkit'; +import { combineReducers, configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; +import queuesSlice from 'store/queuesSlice'; import appReducer from './appSlice'; export const rootReducer = combineReducers({ appReducer, + queues: queuesSlice, }); export const store = configureStore({ reducer: rootReducer, + middleware: getDefaultMiddleware({ + serializableCheck: { + // Ignore auth in async thunks + ignoredActionPaths: ['meta.arg.auth'], + }, + }), }); diff --git a/simplq/src/store/queuesSlice.js b/simplq/src/store/queuesSlice.js new file mode 100644 index 00000000..61a90060 --- /dev/null +++ b/simplq/src/store/queuesSlice.js @@ -0,0 +1,31 @@ +/* eslint-disable no-param-reassign */ + +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { QueueRequestFactory } from 'api/requestFactory'; +import makeAuthedRequest from 'api/axios-alt'; + +const fetchQueues = createAsyncThunk('queues/requestStatus', async (arg) => { + const { auth } = arg; + const authedRequest = makeAuthedRequest(auth, QueueRequestFactory.getMyQueues()); + const response = await authedRequest; + return response; +}); + +const queuesSlice = createSlice({ + name: 'queues', + initialState: [], + // No reducers for now + reducers: {}, + extraReducers: { + // handle fulfiled request + [fetchQueues.fulfilled]: (state, action) => { + return action.payload.queues; + }, + }, +}); + +export const { queuesFetched } = queuesSlice.actions; + +export { fetchQueues }; + +export default queuesSlice.reducer;