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,
};
}
}
Related
I'm working on a React Native app. I have a signup screen which has a button, onclick:
const handleClick = (country: string, number: string): void => {
dispatch(registerUser({ country, number }))
.then(function (response) {
console.log("here", response);
navigation.navigate(AuthRoutes.Confirm);
})
.catch(function (e) {
console.log('rejected', e);
});
};
The registerUser function:
export const registerUser = createAsyncThunk(
'user/register',
async ({ country, number }: loginDataType, { rejectWithValue }) => {
try {
const response = await bdzApi.post('/register', { country, number });
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.message);
}
},
);
I have one of my extraReducers that is indeed called, proving that it's effectively rejected.
.addCase(registerUser.rejected, (state, {meta,payload,error }) => {
state.loginState = 'denied';
console.log(`nope : ${JSON.stringify(payload)}`);
})
But the signup component gets processed normally, logging "here" and navigating to the Confirm screen. Why is that?
A thunk created with createAsyncThunk will always resolve but if you want to catch it in the function that dispatches the thunk you have to use unwrapResults.
The thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate.
The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an unwrapResult function that can be used to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:
import { unwrapResult } from '#reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then(originalPromiseResult => {})
.catch(rejectedValueOrSerializedError => {})
}
I am new to redux. So after reading lots of tutorials.I understood that, redux need redux thunk to dispatch async actions by returning another function.But if I call http request inside custom middle-ware then
is it required redux thunk ?
is Redux custom middleware no side effects ? I mean no need to return another function.
If i use redux thunk then my action creator looks like this. This I understood
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
I have confusion in custom middle-ware.
https://blog.logrocket.com/managing-asynchronous-actions-in-redux-1bc7d28a00c6/
as per this blog
const httpMiddleware = store => next => action => {
if (action[HTTP_ACTION]) {
const actionInfo = action[HTTP_ACTION];
const fetchOptions = {
method: actionInfo.verb,
headers: actionInfo.headers,
body: actionInfo.payload || null
};
next({
type: actionInfo.type + "_REQUESTED"
});
fetch(actionInfo.endpoint, fetchOptions)
.then(response => response.json())
.then(data => next({
type: actionInfo.type + "_RECEIVED",
payload: data
}))
.catch(error => next({
type: actionInfo.type + "_FAILED",
payload: error
}));
} else {
return next(action);
}
}
they are not returning any dispatch function inside action. I know that store,next,action are the inner functions.
can any one help me to understand about this?
Thank you.
All redux-thunk is is a simple redux middleware that checks if your action is a function and acts accordingly. You can build it yourself in 5 minutes.
You can see that it deconstructs dispatch and getState from the store object, and then calls your action with them as parameters.
Have a look at it's source code.
So, your example code can look like this:
const httpMiddleware = store => next => action => {
if (action[HTTP_ACTION]) {
const actionInfo = action[HTTP_ACTION];
const fetchOptions = {
method: actionInfo.verb,
headers: actionInfo.headers,
body: actionInfo.payload || null
};
store.dispatch({
type: actionInfo.type + "_REQUESTED"
});
fetch(actionInfo.endpoint, fetchOptions)
.then(response => response.json())
.then(data => store.dispatch({
type: actionInfo.type + "_RECEIVED",
payload: data
}))
.catch(error => store.dispatch({
type: actionInfo.type + "_FAILED",
payload: error
}));
} else {
return next(action);
}
}
As you asked by point list I'm gonna reply by point list:
If i call http request inside custom middleware then is it required
redux thunk ?
Redux thunk is a middleware per se, it's recommended if you want to dispatch an action that make (let's say, for example) an AJAX call and dispatch two different action based on the result of that AJAX call, one if it fails and one if it succeeds.
is Redux custom middleware no side effects ? i mean no need to return another function.
If I'm understanding correctly: A middleware will simply take the action you dispatched and pass it through the chain, it'll let you do something before sending the action to the reducer "phase". What it'll do is entirely up to you. You need at least to do next(action) or block the action based on some logic, if needed.
Finally, a custom middleware like the one you posted is modifying the action you passed based on the response of an AJAX call, making it more of an "interceptor" than a simple middleware. Written this way, it's not dispatching a new action, it's more related to modifying the action you passed.
If you want to dispatch a new action based on the result of an ajax call, but without creating a middleware/interceptor, you could do it this way:
const postSomethingAction = postObject => {
return async dispatch => {
dispatch(postSomethingPending())
const response = await Api.postSomething(postObject)
if (response.message) {
dispatch(postSomethingError(response))
}
else {
dispatch(postSomethingSuccess(response.data))
}
}
}
In this example we're using Thunk do create an action that dispatch another action based on the result of Api.postSomething
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 building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}
I'm using
hashHistory.push('/home')
To navigate to some route on my app. However, whenever I use it I get
Reducers may not dispatch actions.
How can I properly navigate to some route without getting this error?
Assuming you are using Redux, you can use the thunk middleware and react-router-redux
https://github.com/gaearon/redux-thunk
https://github.com/reactjs/react-router-redux
This will allow you to dispatch an action, reduce it and then dispatch another action afterwards
import { push } from 'react-router-redux'
const someAction = (somePayload) => (dispatch, getState) => {
dispatch({
type: 'SOME_ACTION',
payload: {
somePayload
}
});
// Get the updated state from the store and navigate
let { someVar } = getState();
if (someVar === 'some value') {
dispatch(push('/home'));
}
};
// Call like so
store.dispatch(someAction())
If you're not using redux or don't want to go that route, make sure you are not dispatching an action inside a reducer or part of that action cycle
not the best solution but it works. inside reducer
setTimeout(()=>{
store.dispatch(
//
)
},1);
In my case, I was dispatching an action right after my reducer, through my http request, which was set to async: false:
const reducer = (state, action) => {
...
makeRequest();
return {
...state
}
}
makeRequest = () => {
$.ajax({
type: 'POST',
url: `MYURL`,
async: false,
data: {
...
},
success: function(response) {
store.dispatch({
// new action dispatch here...
});
}
}
)}
By changing async to true async: true, I was able to dispatch an action on the successful case of my ajax request.
I have faced this problem after upgrading react-redux 7.1.0, redux-form to 8.3.8 and the solution is to pass mapDispatchToProps as a function not an object like the snippet below.
On some pages, it is okay with an object but if you face a problem convert it to function.
More info, read https://react-redux.js.org/using-react-redux/connect-mapdispatch.
const mapDispatchToProps = (dispatch) => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
If it solved your issue, please mark my answer as a correct answer.