Converting functions from pure react to redux react - javascript

In pure react, I have written a function that I call in componentDidMount ():
getTasks = (userId, query, statusTask, pageNumber) => {
let check = {};
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(res => {
check = res.data;
if (res.data) {
this.setState({
checkRunning: res.data,
checkRunningId: res.data.id
});
this.utilizeTimes(res.data.task_id);
}
})
.catch(error => {
console.log(error);
})
.then(() => {
const params = {
sort: 'name'
};
if (query) {
params['filter[qwp]'] = query;
if (this.state.tasks[0]) {
this.setState({
selectedId: this.state.tasks[0].id,
selectedTabId: this.state.tasks[0].id
});
}
}
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(res => {
if (res.status === 200 && res.data) {
this.setState({
tasks: res.data,
lengthArrayTasks: parseInt(res.headers['x-pagination-total-count'])
});
if (!check && res.data && res.data[0]) {
this.setState({
selectedTabId: res.data[0].id,
});
this.load(res.data[0].id);
}
let myArrayTasks = [];
myArrayTasks = res.data;
let findObject = myArrayTasks.find(task => task.id === this.state.runningTimerTask.id);
if (
!findObject &&
this.state.runningTimerTask &&
this.state.runningTimerTask.id &&
this.state.query === ''
) {
this.setState({
tasks: [this.state.runningTimerTask, ...myArrayTasks]
});
}
}
})
.catch(error => {
console.log(error);
});
});
};
I am trying to rewrite it to redux, but with poor results. First it makes one request / api / v1 / beta / $ {userId}, writes the answer in the variable check. check passes to the nextthen. In the next then carries out the request '/ api / v1 // tasks' Can somebody help me? I am asking for some tips. Is this somehow complicated?
So far, I've managed to create something like this:
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
actions
export const RUNNING_TIMER = 'RUNNING_TIMER';
export const GET_TASKS = 'GET_TASKS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const runningTimer = (userId, query, statusTask, pageNumber) => dispatch => {
console.log(userId);
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
})
.then(() => {
const params = {
sort: 'name'
};
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(({ data }) => {
dispatch({
type: GET_TASKS,
payload: data
});
})
.catch(error => {
console.log(error);
});
});
};
reducer
import { RUNNING_TIMER, GET_TASKS } from '../actions';
const isRunningTimer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case RUNNING_TIMER:
return {
checkRunningTimer: payload,
checkRunningTimerId: payload && payload.id ? payload.id : null
};
break;
case GET_TASKS:
return {
tasks: payload,
lengthArrayTasks: parseInt(action.headers['x-pagination-total-count'])
};
default:
return state;
}
};
const rootReducer = combineReducers({ isRunningTimer });
export default rootReducer;
App
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
componentDidMount() {
this.props.runningTimer();
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = state => {
const { isRunningTimer } = state;
return {
isRunningTimer
};
};
const mapDispatchToProps = dispatch => ({
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

Number 1 Consider your state design.
I find it useful to consider what the state object would look like at a given point in time.
Here is an example of initialState used in an application of mine.
const initialState = {
grocers: null,
coords: {
latitude: 37.785,
longitude: -122.406
}
};
This is injected at the createStore.
Breaking down your application state object/properties, should assist you in making your actions simpler as well.
Number 2
Consider breaking down your actions.
My thoughts, decouple the action code, at the .then at the second .then .(Consider saving the results somewhere in a user: object)
.then(response => {
const data = response.data.user;
setUsers(data);})
.catch(error => {
console.log('There has been a problem with your fetch operation: ' + error.message);
})
function setUsers(data){
dispatch({
type: FETCH_USERS,
payload: data
});
}
This refers to the S in SOLID design principles. Single Responsibility Principle.
https://devopedia.org/solid-design-principles
Number 3
Consider this, if the 'getUser' info fetch fails.
Having the process/response separated will allow the application to be debugged more cleanly. In example, the user api failed or the getTask api failed, etc.
More resources on redux.
https://redux.js.org/introduction/learning-resources#thinking-in-redux

Extending previous answer from #Cullen, this is what I did:
Since you already have a action to GET_TODOS, just make the action creator for runningTimer to do one and only one thing - make API call to /api/v1/beta/<userId> and dispatch respective actions.
export const runningTimer = (
userId,
query,
statusTask,
pageNumber
) => dispatch => {
return axios({
url: `/api/v1/beta/${userId}`,
method: "GET"
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
});
};
Update props of your app component to read store data.
...
const mapStateToProps = state => {
const { isRunningTimer, todos, todo } = state;
return {
todos,
todo,
isRunningTimer,
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id)),
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer(userId)),
});
...
Update the implementation of componentDidMount to dispatch isRunningTimer -
componentDidMount() {
...
// call with userId 1
this.props.runningTimer(1).then(() => {
console.log(this.props);
// additional params for getTasks
const params = {
sort: 'name'
};
// another call for getTodos with names sorted
this.props.getTodos(params);
});
...
Note: You need to update your getTodos action to take in an optional params arguments (which is initialized to empty object if not passed).
Hope this helps you.
Live sandbox for this is present here - https://stackblitz.com/edit/react-redux-more-actions

Check out React-boilerplate. Great boilerplate for react and redux. They use redux-saga and redux-hooks as well.

Related

[Redux][Axios][React] Adding redux state inside of a axios / action file

I want to add/change a redux state when the data is received from the backend. This state controls a loading spinner.
The code below is what I thought that should work.
What am I missing?
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
var tempx = {show: false};
addStateLoading(tempx);
})
.catch(error => {
toastOnError(error);
});
};
A simple way of solving this kind issue, create custom hook for all services and where ever you need it.
export const useCouriers = () => {
const dispatch = useDispatch();
const getCouriers = async () => {
try {
dispatch(addStateLoading({ show: true }));
const response = await axios.get("/api/v1/couriers/");
dispatch({
type: GET_COURIERS,
payload: response.data,
// I think this should be response.data.data
});
} catch (error) {
toastOnError(error);
} finally {
dispatch(addStateLoading({ show: false }));
}
};
return { getCouriers };
};
Inside component
const { getCouriers } = useCouriers();
// call where you need
If you want to use redux, check redux-toolkit, it helps a lot the development with redux.
https://redux-toolkit.js.org/
#rahul-sharma answer helped me to find this answer. I just called addStateLoading inside of dispatch.
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
dispatch(addStateLoading({ show: false }));
})
.catch(error => {
toastOnError(error);
});
};

Passing params from one Axios request to another

Background
I'm connecting an app built in React Native to a REST API. I'm handling requests via Axios and storing the results from queries with Redux. I have an index.js file for my api connections which holds the functions that act as handlers for requests which require deeper and deeper levels of authorization. I have a simple function which returns an access token, this is triggered by the following code which currenty is located in the app's "Welcome page".
useEffect(() => {
dispatch(retrieveToken());
}, [dispatch]);
Ideally, after navigating through a couple of screens, the user would get to the Home Page and trigger the following code:
useEffect(() => {
dispatch(retrieveData());
}, [dispatch]);
So far, so good. These are the functions which dispatch triggers:
export const getToken = () =>
apiInstance
.request({
url: ENDPOINTS.TOKEN,
data: qs.stringify({
grant_type: 'some_credentials',
c_id: 'some_id',
c_secret: 'some_secret',
}),
headers: {
'content-type': 'some_content_type',
},
method: 'POST',
})
.then(response => {
return response.data;
})
.catch(error => {
return Promise.reject(error.message);
});
export const getData = () =>
apiInstance
.request({
url: ENDPOINTS.DATA,
method: 'POST',
data: qs.stringify({
timestamp: Date.now(),
c_id: 'some_id',
token: **this is the token we get from the previous function**,
}),
headers: {
'content-type': 'some_content_type',
},
})
.then(response => {
return response.data;
})
.catch(error => {
return Promise.reject(error.message);
});
Problem
As I mentioned before, this is a Redux/Axios solution. This means state is stored globally but there is an order of execution. You should note that these two functions are stored within the same file and are not called upon unless explicitly stated such as with the two dispatch calls I've showed before.
Thing is, if I log the token from Home (after calling it with dispatch) I can see it clearly, however if I try to log said token from the file which stores the request functions, I get an empty array. I've tried to fill the token field in all the following ways:
const state = store.getState()
token: state.token
const getData = (Token) =>{
...
token: Token}
And passing Token as a param within dispatch.
I've also tried daisy-chaining the different dispatches in order to force the execution of
getData after retrieving the token and not before.
Question
How can I access the result of an axios query from within another, in specific order?
It is very important to note that the data in the API can only be accessed via POST and that the error code I get when executing getData() is 401, incorrect credentials.
Also, remember this is a Redux application. The results of the queries are stored withing a global state. However this state cannot be accessed from outside components, and I cannot call it from the file in which the queries are executed given the token "does not exist at that point in time."
Action code
import keyMirror from 'keymirror';
import {createAction} from 'redux-actions';
import {getToken} from '../../api';
export const tokenActionTypes = keyMirror({
RETRIEVE_TOKEN_REQUEST: null,
RETRIEVE_TOKEN_SUCCESS: null,
RETRIEVE_TOKEN_FAILURE: null,
});
const tokenActionCreators = {
request: createAction(tokenActionTypes.RETRIEVE_TOKEN_REQUEST),
success: createAction(tokenActionTypes.RETRIEVE_TOKEN_SUCCESS),
failure: createAction(tokenActionTypes.RETRIEVE_TOKEN_FAILURE),
};
export const retrieveToken = () => dispatch => {
dispatch(tokenActionCreators.request());
getToken()
.then(token => dispatch(tokenActionCreators.success(token)))
.catch(error => dispatch(tokenActionCreators.failure(error)));
};
Reducer code
import {tokenActionTypes} from '../actions/token';
export const initialState = {
loadingToken: false,
token: [],
error: null,
};
const actionsMap = {
[tokenActionTypes.RETRIEVE_TOKEN_REQUEST]: state => ({
...state,
loadingToken: true,
}),
[tokenActionTypes.RETRIEVE_TOKEN_SUCCESS]: (state, action) => ({
...state,
loadingToken: false,
token: action.payload,
}),
[tokenActionTypes.RETRIEVE_TOKEN_FAILURE]: (state, action) => ({
...state,
loadingToken: false,
error: action.payload,
}),
};
export default (state = initialState, action) => {
const actionHandler = actionsMap[action.type];
if (!actionHandler) {
return state;
}
return actionHandler(state, action);
};
You could combine one thunk in another, like combining get token in get data:
export const retrieveToken = () => (dispatch, getState) => {
//you could use getState() to see if you need to fetch the token
// const tokenResult = selectToken(getState());
// if(token && !token expired) { return Promise.resolve() }
dispatch(tokenActionCreators.request());
//return a promise so you can wait for it
return getToken()
.then(token => dispatch(tokenActionCreators.success(token)))
.catch(error => dispatch(tokenActionCreators.failure(error)));
};
//in retrieve data you can wait for the token
export const retrieveData = () => dispatch => {
dispatch(retrieveToken()).then(
()=>{
//here return getting the data
}
)
};
A possible bug in that code is that one render cycle will dispatch multiple thunks that will get the token multiple times. You can solve that by grouping the retrieveToken action with a cache that invalidates on resolve:
const invalidateOnResolveCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (x) => cache.delete(key),
};
};
Or you can write a wrap function for all thunks that need a token:
//group retrieveToken in such a way that if it's called multiple times
// during a render cycle the token request will only be made once
//https://gist.github.com/amsterdamharu/2dde4a6f531251f3769206ee44458af7
export const needsToken =
(fn) =>
(...args) =>
(dispatch, getState) =>
dispatch(retrieveToken(...args)).then(() =>
//you could use getState to get the token and pass it to
// fn together with the other args
// for example: fn(...args.concat(selectToken(getState())))
fn(...args)
);
export const autoTokenRetrieveData = needsToken(retrieveData);
//use needsToken for any other thunk actions that need a token
Example:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;
//grouping code to group your actions
//group promise returning function
const createGroup =
(cache) =>
(fn, getKey = (...x) => JSON.stringify(x)) =>
(...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.resolved(key); //tell cache promise is done
return r;
},
(e) => {
cache.resolve(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//thunk action creators are not (...args)=>result but
// (...args)=>(dispatch,getState)=>result
// so here is how we group thunk actions
const createGroupedThunkAction = (thunkAction, cache) => {
const group = createGroup(cache)(
(args, dispatch, getState) =>
thunkAction.apply(null, args)(dispatch, getState)
);
return (...args) =>
(dispatch, getState) => {
return group(args, dispatch, getState);
};
};
const createInvalidateOnResolveCache = (
cache = new Map()
) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
resolved: (key) => cache.delete(key),
};
};
//function that fetches token
const uniqueToken = (
(token) => () =>
token++
)(1);
const fetchToken = () => Promise.resolve(uniqueToken());
const initialState = {
data1: [],
data2: [],
token: null,
};
//action types
const DATA_SUCCESS = 'DATA_SUCCESS';
const GOT_TOKEN = 'GOT_TOKEN';
//action creators
const dataSuccess = (data, key) => ({
type: DATA_SUCCESS,
payload: { key, data },
});
const gotToken = (token) => ({
type: GOT_TOKEN,
payload: token,
});
const reducer = (state, { type, payload }) => {
if (type === DATA_SUCCESS) {
const { data, key } = payload;
return {
...state,
[key]: data,
};
}
if (type === GOT_TOKEN) {
return {
...state,
token: {
value: payload,
created: Date.now(),
},
};
}
return state;
};
//thunk getting the data
const getData1 = (token) => (dispatch) =>
Promise.resolve().then(() =>
dispatch(
dataSuccess(
`got data 1 with token: ${token}`,
'data1'
)
)
);
const getData2 = (token) => (dispatch) =>
Promise.resolve().then(() =>
dispatch(
dataSuccess(
`got data 2 with token: ${token}`,
'data2'
)
)
);
//thunk getting the token:
const getToken = () => (dispatch) =>
fetchToken().then((token) => dispatch(gotToken(token)));
//grouped thunk getting token
const getTokenGrouped = createGroupedThunkAction(
getToken,
createInvalidateOnResolveCache()
);
const needsToken =
(fn) =>
(...args) =>
(dispatch, getState) => {
let promise;
//only fetch token if it's older than 1 second
const tokenResult = selectToken(getState());
if (
tokenResult &&
Date.now() - new Date(tokenResult.created).getTime() <
1000
) {
promise = Promise.resolve();
} else {
promise = dispatch(getTokenGrouped(...args));
}
return promise.then(() =>
dispatch(
fn(...args.concat(selectTokenValue(getState())))
)
);
};
const getData1WithToken = needsToken(getData1);
const getData2WithToken = needsToken(getData2);
//selectors
const selectData1 = (state) => state.data1;
const selectData2 = (state) => state.data2;
const selectToken = (state) => state.token;
const selectTokenValue = createSelector(
[selectToken],
//SO snippet has no optional chaining, should just return token?.value
(token) => token && token.value
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(
//simple thunk middleware
({ dispatch, getState }) =>
(next) =>
(action) =>
typeof action === 'function'
? action(dispatch, getState)
: next(action)
)
)
);
const Data1 = React.memo(function Data1({ refresh }) {
const data = useSelector(selectData1);
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(getData1WithToken());
}, [dispatch, refresh]);
return <div>{data}</div>;
});
const Data2 = React.memo(function Data2({ refresh }) {
const data = useSelector(selectData2);
const dispatch = useDispatch();
React.useEffect(() => {
dispatch(getData2WithToken());
}, [dispatch, refresh]);
return <div>{data}</div>;
});
const App = () => {
const [refresh, setRefresh] = React.useState({});
return (
<div>
{/* getting data in one render cycle many times */}
<Data1 refresh={refresh} />
<Data2 refresh={refresh} />
<Data1 refresh={refresh} />
<Data2 refresh={refresh} />
<Data1 refresh={refresh} />
<Data2 refresh={refresh} />
<button onClick={() => setRefresh({})}>
refresh
</button>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<script src="https://unpkg.com/immer#7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Explanation:
Everywhere you see const add export so export const or export default and you can import that from any other file.
The createGroupedThunkAction receives getToken thunk and returns a thunk that is stored in getTokenGrouped.
When getTokenGrouped is called multiple times during one render (Data1 and Data2 have an effect that will do so) it will share getting the token for that render and when it resolves it'll delete the cache because of the type of cache used implemented in createInvalidateOnResolveCache. So no multiple tokens will be fetched during one render no matter how many times you dispatch it during a render.
The needsToken function will receive a thunk (getData1 and getData2) and returns a thunk that will automatically get a token by dispatching getTokenGrouped if there is no current token or if the token is older than one second (my made up logic to invalidate the token). This token is stored in state and passed to getData1 and getData2 so they can use that token.
I suggest opening the redux devtools while running the example so you can see the actions being dispatched. Normally with async you would dispatch beforeFetch, afterFetch or faildFetch for async actions but for simplicity I left that out.
You could try to use createGroupedThunkAction to make a grouped getData1 and getData2 as an exercise so there is no needless fetching for these as well.

redux thunk fetch api action and reducer

So decided to use redux-thunk and I have a problem to write a function in my actions and reducer. Actually function looks like this:
async getData() {
if (this.props.amount === isNaN) {
return;
} else {
try {
await fetch(
`https://api.exchangeratesapi.io/latest?base=${this.props.base}`,
)
.then(res => res.json())
.then(data => {
const date = data.date;
const result = (data.rates[this.props.convertTo] * this.props.amount).toFixed(4);
this.setState({
result,
date,
});
}, 3000);
} catch (e) {
console.log('error', e);
}
}
}
Also I already have action types
export const FETCH_DATA_BEGIN = 'FETCH_DATA_BEGIN';
export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS';
export const FETCH_DATA_FAIL = 'FETCH_DATA_FAIL';
and actions like this
export const fetchDataBegin = () => {
return {
type: actionTypes.FETCH_DATA_BEGIN,
};
};
export const fetchDataSuccess = data => {
return {
type: actionTypes.FETCH_DATA_SUCCESS,
data: data,
};
};
export const fetchDataFail = error => {
return {
type: actionTypes.FETCH_DATA_FAIL,
error: error,
};
};
And then comes the hard part for me where I don't know how to get the same result from function async getData(). I already have just this in my action :
export async function fetchData() {
return async dispatch => {
return await fetch(`https://api.exchangeratesapi.io/latest?base=${this.props.base}`)
.then(res => res.json())
.then(data => {
// <------------------- WHAT NEXT?
}
};
export function fetchData() {
return dispatch => {
fetch(`https://api.exchangeratesapi.io/latest?base=${this.props.base}`)
.then(res => res.json())
.then(data => dispatch(fetchDataSuccess(data)), e => dispatch(fetchDataFail(e)))
}
};
Now this code:
const date = data.date;
const result = (data.rates[this.props.convertTo] * this.props.amount).toFixed(4);
this.setState({
result,
date,
});
goes into your reducer
if(action.type === FETCH_DATA_SUCCESS) {
const date = action.data.date;
const rates = action.data.rates;
return { ...state, rates, date };
}
Now you can use the redux state in your component and make the rest of the calculations there (ones that need this.props).
To dispatch the fetchData action now, you do this.props.dispatch(fetchData()) in your react-redux connected component.
EDIT
Here's how you use the state in the component.
I'm assuming you have created the redux store. something like:
const store = createStore(rootReducer,applyMiddleware(thunk));
Now, you can use the react-redux library's connect function to connect the redux state to your component.
function mapStateToProps(state, ownProps) {
return {
date: state.date,
result: (state.rates[ownProps.convertTo] * ownProps.amount).toFixed(4);
}
}
function mapDispatchToProps(dispatch) {
return {
fetchData: () => dispatch(fetchData())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(YourComponent)
You can use this Higher Order Component in your DOM now and pass the appropriate props to it:
import ConnectedComponent from "./pathTo/ConnectedComponent";
...
return <View><ConnectedComponent convertTo={...} amount={...} /></View>;
And, also inside YourComponent you can now read this.props.date and this.props.result and use them wherever you need to.
You might want to look at selectors in the future to memoize the state and reduce the performance cost of redux.

React-native redux - this.props are undefined from AsyncStorage

Being a newbie with RN and Redux, I'm confused as to why my props are undefined after reading from AsyncStorage.
I log in, save the state to the store and storage... I reload the app and read from the storage and update the state. The storage is retrieving my object but the props are undefined.
actions.js:
export const getSession = (data) => ({
type: 'GET_SESSION',
payload: {
user: data
}
});
export const getUserSession = () => dispatch => {
return AsyncStorage.getItem('userSession').then((data) => {
console.log('Props at asynsstorage: ', data);
// {"current_user":{"uid":"1","roles":["authenticated","administrator"], ...}
dispatch(loading(false));
dispatch(getSession(data));
})
.catch((err) => {
})
}
reducer.js
import { combineReducers } from 'redux';
const defaultState = {
xcsrf: '',
user: {},
loading: false,
error: '',
};
const authReducer = ( state = defaultState, action ) => {
switch(action.type) {
case 'GET_SESSION':
return {
...state,
user: action.payload.user,
loading: false,
}
case 'SAVE_SESSION':
return {
...state,
user: action.payload.user,
loading: false,
}
default:
return state;
}
}
export default combineReducers({
authReducer: authReducer
});
authLoading.js // screen
class AuthLoadingScreen extends React.Component {
constructor() {
super();
}
componentDidMount = () => {
this.props.getUserSession().then(() => {
console.log( 'Props at loading: ', this.props.user );
// undefined
})
.catch(error => {
})
};
// Render any loading content that you like here
render() {
return ();
}
}
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
getUserSession: () => dispatch(getUserSession()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
You cannot access directly user of reducer. So change
const mapStateToProps = state => ({
user: state.user,
});
To
const mapStateToProps = state => ({
user: state.authReducer.user,
});
And one more thing AsyncStorage's getItem() method return string of stored data. You have not converted to it json. So please also convert that as below :
export const getUserSession = () => dispatch => {
return AsyncStorage.getItem('userSession').then((data) => {
console.log('Props at asynsstorage: ', data);
// {"current_user":{"uid":"1","roles":["authenticated","administrator"], ...}
dispatch(loading(false));
dispatch(getSession(JSON.parse(data))); //convert to json here
})
.catch((err) => {
})
}

React/Redux chaining async thunk actions at component level

What is the recommended way to chain dependent asynchronous redux thunk actions at the component level?
My use case is a flow where I need to first make an api call to retrieve a user object, then grab all blog posts for that user. The catch is that the second call to grab all blog posts is dependent on the first calls return value (user id).
My component:
export default class UserDetail extends React.Component
{
componentDidMount() {
this.props.getUser()
}
}
this.props.getUser() returns a user object which I map to props:
const mapStateToProps = (state) => {
return {
user: state.user
}
}
I need to call this.props.getBlogPostsForUser(USER_ID) after this.props.getUser() has completed. What is the recommended best practice to chain actions in this way?
You can chain thunks
const getUser = username => dispatch => request(username)
.then(res => dispatch({ type: GET_USER })
.catch(err => dispatch({ type: GET_USER_ERR }));
const getBlogPostsForUser = userId => dispatch => request(userId)
.then(res => dispatch({ type: GET_BLOGS }))
.catch(err => dispatch({ type: GET_BLOGS_ERR }));
const getUserAndPosts = username => (dispatch, getState) => dispatch(getUser(username))
.then(() => {
const user = getState().user;
return dispatch(getBlogPostsForUser(user.id));
});
Or you can combine them to one dispatch but then they are tied together
const getUserAndPosts = (username) => dispatch => request(username)
.then((userData) => {
dispatch(setUser(userData));
return request(user.id)
.then(blogs => dispatch(setBlog(blogs)));
});
You have to identify the new user response coming in componentDidUpdate lifecycle method to call another dependent call. Like this
export default class UserDetail extends React.Component {
componentDidMount() {
this.props.getUser();
}
componentDidUpdate(prevProps) {
const { user, getBlogPostsForUser } = this.props;
const { user: prevUser } = prevProps;
if (prevUser !== user) {
const { USER_ID } = user; // derive USER_ID from user object. I dont know path. you can accordingly change
getBlogPostsForUser(USER_ID);
}
}
}
This should work. Feedbacks are welcome

Categories

Resources