I have a functional component in a react native application, and I'm dispatching an HTTP call. If some error occurs, I store it in redux, the problem is when I access to the error value, I get null
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
const response = await actions
.accept(token, orderId, coords) //this is the async function
.catch((e) => {
showError(props.error);
});
}
...
}
...
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
accept: (token, id, coords) => {
return dispatch(Order.accept(token, id, coords));
}
}
};
}
The problem is most likely on acceptOrder() async action creator.
Redux dispatch wont update immediately after your promise resolves/rejects. So by the time your error handler (Promise.prototype.catch or catch(){}) kicks in, there is no guarantee the action has been dispatched or the state tree updated.
What you want to do instead is to have this logic on the async action creator
// action module Order.js
export const accept = (token, id, coords) => dispatch => {
fetch('/my/api/endpoint')
.then(response => doSomethingWith(response))
.catch(err => dispatch(loadingFailed(err)); // <----- THIS is the important line
}
// reducer.js
// you want your reducer here to handle the action created by loadingFailed action creator. THEN you want to put the error in the state.
// IncomingOrder.js
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
actions.accept(token, orderId, coords);
// You don't need to wait for this, as you're not gonna work with the result of this call. Instead, the result of this call is put on the state by your reducer.
}
render() {
const { error } => this.props; // you take the error from the state, which was put in here by loadingFailed()
if (error) {
// render your error UI
} else {
// render your regular UI
}
}
}
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error // <--- I suppose your error is already here (?)
};
}
Related
State is not updated immediately after receiving data
Accounts.js like this
class Accounts extends Component {
componentDidMount()
{
this.props.dispatch(fetchAccountsAction())
}
render(){
const accInfo = this.props.accounts // Not getting data immediately
return (
<Details accInfo = {accInfo} />
)
}
}
const mapStateToProps = (state, ownProps) => {
console.log('state',state);
return {
accounts:state.accounts
}
}
export default connect(mapStateToProps)(Accounts)
Action.js like this
const fetchAccountsAction = () => {
return async (dispatch) => {
const res = await fetch(url, {
method: "POST",
headers: {
'Content-type': 'Application/json',
'Authorization': token,
},
body: JSON.stringify(data)
});
const data = await res.json()
if (data) {
dispatch(fetchAccounts(data))
}
}
}
export function fetchAccounts(accounts)
{
console.log('accounts',accounts) // Am getting data here
return {
type: FETCH_ACCOUNTS,
accounts : accounts
}
}
Reducer.js like this
const initialState = {
accounts : [],
error:null
}
export function accountsReducer(state=initialState,action) {
switch(action.type){
case FETCH_ACCOUNTS:
return {
...state,
accounts:action.accounts
}
default:
return state
}
}
When componentDidMount happened props not receiving immediately because there is a delay in API response. Could you please help with the props access after receiving the data from API.
Thank you.
What happens here:
cDM is called, action is dispatched.
If action creator was sync(just a plain action + straight reducer without any async operations) state would be updated
render() happens with previous props(old state)
redux's store.subscribe() makes wrapper(created by connect) to recalculate all that mapStateToProps/mapDispatchToProps
since step #3 returned different values wrapper re-renders your component with new props
render() happens with new props
That fact your action creator is async by its nature switch #2 and #3 with their places. But anyway, your first render will be with old store values.
So you better handle that accordingly(like checking if some object is not undefined anymore or use brand new optional chaining to get safe from "cannot read property ... of null")
I am using react-redux in my application. It communicates with backend API. When there is any error it should dispaly an alert message.
Follwing is my action:
function getByStatus(code) {
return dispatch => {
StoreService.getByStatus(code)
.then(
users => {
......
}
).catch(error => {
dispatch(failure(error));
dispatch(alertActions.error(error)); // problem here
console.log(error);
});
};
function failure(error) { return { type:Constants.GETALL_FAILURE, error } }
}
if i add dispatch(alertActions.error(error)) in the catch the code is making repeated call to the api.
I am calling my action in componentWillMount like below
componentWillMount() {
const { dispatch } = this.props;
dispatch(ReviewActions.getByStatus(1));
}
when i remove dispatch(alertActions.error(error)), It is not making repeated call.
Can you please hlep me
How can i dispatch alert action in my code ?
What went wrong when i
add alert action dispatch?
I have the following middleware that I use to call similar async calls:
import { callApi } from '../utils/Api';
import generateUUID from '../utils/UUID';
import { assign } from 'lodash';
export const CALL_API = Symbol('Call API');
export default store => next => action => {
const callAsync = action[CALL_API];
if(typeof callAsync === 'undefined') {
return next(action);
}
const { endpoint, types, data, authentication, method, authenticated } = callAsync;
if (!types.REQUEST || !types.SUCCESS || !types.FAILURE) {
throw new Error('types must be an object with REQUEST, SUCCESS and FAILURE');
}
function actionWith(data) {
const finalAction = assign({}, action, data);
delete finalAction[CALL_API];
return finalAction;
}
next(actionWith({ type: types.REQUEST }));
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
I am then making the following calls in componentWillMount of a component:
componentWillMount() {
this.props.fetchResults();
this.props.fetchTeams();
}
fetchTeams for example will dispatch an action that is handled by the middleware, that looks like this:
export function fetchTeams() {
return (dispatch, getState) => {
return dispatch({
type: 'CALL_API',
[CALL_API]: {
types: TEAMS,
endpoint: '/admin/teams',
method: 'GET',
authenticated: true
}
});
};
}
Both the success actions are dispatched and the new state is returned from the reducer. Both reducers look the same and below is the Teams reducer:
export const initialState = Map({
isFetching: false,
teams: List()
});
export default createReducer(initialState, {
[ActionTypes.TEAMS.REQUEST]: (state, action) => {
return state.merge({isFetching: true});
},
[ActionTypes.TEAMS.SUCCESS]: (state, action) => {
return state.merge({
isFetching: false,
teams: action.payload.response
});
},
[ActionTypes.TEAMS.FAILURE]: (state, action) => {
return state.merge({isFetching: false});
}
});
The component then renders another component that dispatches another action:
render() {
<div>
<Autocomplete items={teams}/>
</div>
}
Autocomplete then dispatches an action in its componentWillMount:
class Autocomplete extends Component{
componentWillMount() {
this.props.dispatch(actions.init({ props: this.exportProps() }));
}
An error happens in the autocomplete reducer that is invoked after the SUCCESS reducers have been invoked for fetchTeams and fetchResults from the original calls in componentWillUpdate of the parent component but for some reason the catch handler in the middleware from the first code snippet is invoked:
return callApi(endpoint, method, data, authenticated).then(response => {
return next(actionWith({
type: types.SUCCESS,
payload: {
response
}
}))
}).catch(error => {
return next(actionWith({
type: types.FAILURE,
error: true,
payload: {
error: error,
id: generateUUID()
}
}))
});
};
I do not understand why the catch handler is being invoked as I would have thought the promise has resolved at this point.
Am not completely sure, it's hard to debug by reading code. The obvious answer is because it's all happening within the same stacktrace of the call to next(actionWith({ type: types.SUCCESS, payload: { response } })).
So in this case:
Middleware: Dispatch fetchTeam success inside Promise.then
Redux update props
React: render new props
React: componentWillMount
React: Dispatch new action
If an error occurs at any point, it will bubble up to the Promise.then, which then makes it execute the Promise.catch callback.
Try calling the autocomplete fetch inside a setTimeout to let current stacktrace finish and run the fetch in the next "event loop".
setTimeout(
() => this.props.dispatch(actions.init({ props: this.exportProps() }))
);
If this works, then its' the fact that the event loop hasn't finished processing when the error occurs and from the middleware success dispatch all the way to the autocomplete rendered are function calls after function calls.
NOTE: You should consider using redux-loop, or redux-saga for asynchronous tasks, if you want to keep using your custom middleware maybe you can get some inspiration from the libraries on how to make your api request async from the initial dispatch.
So in my React component, I have this:
this.props.updateAlertCallback('error', ERROR_MESSAGE)
My updateAlertCallback action is:
export const updateAlert = (alert, message) => {
return {
type: 'UPDATE_ALERT',
alert,
message
}
}
export const updateAlertCallback = (alert, message) => {
return dispatch => {
return dispatch(updateAlert(alert, message)).then(() => {
console.log('Done!');
});
}
}
I'm getting the following error: Uncaught TypeError: dispatch(...).then is not a function
What's the proper way to log something after updateAlert is done running?
With redux-thunk, you can make action return a promise:
export const updateAlert = (alert, message) => (dispatch, getState) => {
dispatch ({
type: 'UPDATE_ALERT',
alert,
message
});
return Promise.resolve(getState());
// or just Promise.resolve();
now you can call updateAlert(xx, xx).then(newState => {.....});
function showAlert(message) {
return {
type: SHOW_ALERT,
message
};
}
function hideAlert(message) {
return {
type: HIDE_ALERT,
};
}
function flashAlert(message) {
return (dispatch) => {
dispatch(showAlert(message));
setTimeout(() => {
dispatch(hideAlert());
}, 5000);
}
}
You'll need redux-thunk for this to work. You can then use this.props.flashAlert('Oh noes!!!!') with the proper mapStateToProps. Also needed are reducers and react components.
Fading isn't necessarily an easy thing to do in react. I suggest you save that for later.
What the flashAlert function does is it returns a function that takes a dispatch function. This function does all kinds of fun things but not yet. First this function gets passed to redux's dispatch. This dispatch would normally throw because actions must be plain objects. But because you're using redux-thunk it will be fine. Redux-thunk will call this function and pass it the dispatch function from redux. Now the function will run, finally. First thing it does is dispatch an action that it gets by calling showAlert(). This time it's an object with a type property, which makes it a proper redux action. Presumably redux will pass this action on to our reducer which will update the state with the new message, but we don't know that for sure because the reducer was left out of this answer for brevity. Who know what code it contains. After the state was changed to show the message somehow, we do a setTimeout(). When this calls back we dispatch another action we get by calling hideAlert() using the same dispatch function we used previously. We still have it. This presumably will scrub the message from the state.
Redux will tell react to rerender the appropriate components whenever the state changes. Presumably one of those components will display or not display the message as the case may be.
Redux-thunk is your answer. In your store code change
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
to
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f,
applyMiddleware(thunk)
);
and you will be able to use thunks with your redux actions.
Refer to https://github.com/gaearon/redux-thunk#installation
Actions in redux are plain objects. Redux thunk allows to return functions instead of objects. These functions are executed by the thunk middleware, and ultimately the final object that reaches the store for dispatch is a plain object. An example of redux thunked action is below.
export default class AccountActions {
static login(username, password) {
return (dispatch, getStore) => {
dispatch(AccountActions.loginRequest(username));
fetch(apiUrls.signInUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
user: {
email: username,
password: password,
}
})
})
.then(response => {
return response.json().then(responseJson => {
return dispatch(AccountActions.loginResponse(username, responseJson.token, response.status));
});
})
.catch(err => {
console.error(err);
});
};
}
static loginRequest(username) {
return {
type: ActionTypes.loginRequest,
username,
};
}
static loginResponse(username, token, status) {
return {
type: ActionTypes.loginResponse,
username,
token,
status,
};
}
}
I am call a promise function in my reducer, so I call it like this:
cart(state, action) {
switch(action.type) {
//...
case LOG_IN:
return getCartProducts(true)
}
}
getCartProducts(isLogin) {
// ....
return cartApi.list({
customerId: "",
items: items
}).then(data => {
return cartReduce(undefined, receiveCartProducts(data.items, false))
})
}
so this just returns a promise, not the object I want to
You should make async actions for these as described in the Redux docs.
import fetch from 'isomorphic-fetch';
// One action to tell your app you're fetching data
export const GET_CART_PRODUCTS_REQUEST = 'GET_CART_PRODUCTS_REQUEST'
function getCartProductsRequest(customerId) {
return {
type: GET_CART_PRODUCTS_REQUEST,
customerId
};
}
// One action to signify success
export const GET_CART_PRODUCTS_SUCCESS = 'GET_CART_PRODUCTS_SUCCESS'
function getCartProductsSuccess(cartProducts) {
return {
type: GET_CART_PRODUCTS_SUCCESS,
cartProducts
};
};
// One action to signify an error
export const GET_CART_PRODUCTS_ERROR = 'GET_CART_PRODUCTS_ERROR'
function getCartProductsSuccess(error) {
return {
type: GET_CART_PRODUCTS_ERROR,
error
};
};
// This is the async action that fires one action when it starts
// And then one of two actions when it finishes
export function fetchCartProducts(customerId) {
return dispatch => {
// Dispatch action that tells your app you're going to get data
// This is where you'll set any 'isLoading' state in your store
dispatch(getCartProductsRequest(customerId))
return fetch('https://api.website.com/customers/' + customerId + '/cartProducts')
.then(response => response.json())
.then(
// Fire success action on success
cartProducts => dispatch(getCartProductsSuccess(cartProducts)),
// Fire error action on error
error => dispatch(getCartProductsError(error))
);
};
};
}