I am developing a react app with redux
I wrote my actions in a separate js file as shown below
function getCity(city,dispatch) {
fetch('https://################', {
method: "GET",
headers: {
"user-key": "#############",
"Content-Type": "application/json",
},
}).then((res) => {
return res.json()
}).then((data) => {
console.log(data.location_suggestions);
dispatch({type:'getting_cities', city:data.location_suggestions});
})
}
then I mapped them with the code below
function mapDispatchToProps(dispatch) {
return {
getLocations:(data) => getCity(data),
logStore: () => dispatch({type:'LOGSTORE'})
}
}
console.log is working great but dispatch is not working.
please help me
It seems like the solution is simple:
You forgot to send dispatch with getCity in the snippet where you are calling your action
// Change this
getCity(data)
// To this
getCity(data, dispatch)
I recommend you to check out the redux docs about Async Actions especially the Async Action Creators section.
Try the following:
function getCity(city) {
return function (dispatch) {
return fetch(...)
.then(res => res.json())
.then(data => dispatch({ ... }));
}
}
You can also use more cool arrow syntax. This is equivalent.
function getCity(city) {
return dispatch =>
fetch(...)
.then(...)
.then(...);
}
Don't forget to export your function since it's in a separate file.
Now to dispatch your action creator in your main file:
import { getCity } from '...';
store.dispatch(getCity('berlin'));
Be sure to use connect if you work with React.
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 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())
}
here is an example:
export const fetchPosts = (path, postData) => {
let url = target + path + Tool.paramType(postData);
return dispatch => {
dispatch(requestPosts(postData));
return fetch(url,{
mode: 'cors',
"Content-Type": "application/json",
})
.then(response => {
if(response.ok){
response.json().then(json => dispatch(receivePosts(path, json)))
}else{
console.log("status", response.status);
}
})
.catch(error => console.log(error))
}
}
when I want to request data in my commponent:
this.props.fetchPosts(this.props.seting.url,this.props.seting.data)
however,when I import like this:
import *as action from '../../Redux/Action/Index';
action.fetchPosts(this.props.seting.url,this.props.seting.data)
project seems to start successfully...Is that right?..... =.=
In order to make fetchPosts available as a prop to your component you need to make use of mapDispatchToProps function and bindActionCreators like
import *as action from '../../Redux/Action/Index';
......
mapDispatchToProps(dispatch) {
return {
fetchPosts: bindActionCreators(action.fetchPosts, dispatch);
}
}
Also you need to make use of connect from redux to bind actions to the component like
export default connect(null, mapDispatchToProps)(componentName);
and this is the correct approach.
import { connect } from 'react-redux'
import {
requestPosts,
receivePosts
} from '../../Redux/Action/Index';
export const fetchPosts = (path, postData) => {
const url = target + path + Tool.paramType( dispatch(requestPosts(postData)) );
const { dispatch } = props
return fetch(url, {
mode: 'cors',
"Content-Type": "application/json",
})
.then(response => {
if(response.ok){
response.json().then(json => dispatch(receivePosts(path, json)))
}else{
console.log("status", response.status);
}
})
.catch(error => console.log(error))
}
export default connect(componentName);
When you use connect, dispatch automatically becomes available in props so you can invoke it directly by including it in your deconstructing of props. It seems that in your code, you are passing postData to the Tool.paramType before it has been defined, and it is not defined until after the return - I can't say that does not work or does, it just seems like it could fail - so I moved that dispatch directly into where the data is needed when it's needed. The answer above mine is also correct, these are just different ways of using dispatch and I have done both - recently I stopped using mapDispatchToProps once i learned it is already available on props via connect except in cases where I need to bindActionCreators , and that is needed when you are passing dispatch down to a lower level component that has no idea of redux.
I have post method helper where I'm making the rest calls to the server which is basically running but the view/container is not rerendering after the call.
export function postData(action, errorType, isAuthReq, url, dispatch, data) {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = {headers: {'Authorization': cookie.load('token')}};
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
}
I'm getting the the following error: dispatch is not defined in the browser when I'm calling this method
my call from the container is as followed:
handleFavorite(buildingId) {
const url = `/building/${buildingId}/toogle-favorite`;
postData(FETCH_All_BUILDING, AUTH_ERROR, true, url, this.props.dispatch, {});
}
This is how my connect method is looks like:
function mapStateToProps(state) {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
export default connect(mapStateToProps, {buildingsAll})(BuildingAll);
My Question is...
How can I re render my view? This dispatch that I want to give to the method is not available. Is there a possibility to bind that rest to the state perhaps with mapDispatchToProps. Any idea how I can solve that problem, I'm fairly new to react/redux - it's my first side project in that lib.
Thanks
Update 1
I have updated the code but getting the next error and my view is now not rendering (nothing showing).
mapDispatchToProps() in Connect(BuildingAll) must return a plain object. Instead received function
bundle.js:26 Uncaught TypeError: finalMergeProps is not a function
const mapDispatchToProps = (dispatch) => bindActionCreators(postDataThunk, dispatch);
export default connect(mapStateToProps, mapDispatchToProps, {buildingsAll})(BuildungAll);
You need to bind your action creators in your container
const { bindActionCreators } = require("redux");
const mapStateToProps = (state) => {
return {
buildings: state.building.buildings,
error: state.building.error,
userId: state.auth.userId
}
}
const mapDispatchToProps = (dispatch) => bindActionCreators(YourActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(BuildingAll);
And then your action becomes something like this:
import thunk from 'redux-thunk';
const postData = (action, errorType, isAuthReq, url, data) => {
return (dispatch) => {
const requestUrl = API_URL + url;
let headers = {};
if (isAuthReq) {
headers = { headers: { 'Authorization': cookie.load('token') } };
}
axios.post(requestUrl, data, headers)
.then((response) => {
dispatch({
type: action,
payload: response.data
});
})
.catch((error) => {
errorHandler(dispatch, error.response, errorType)
});
};
};
Because your postData might have a few side effects because it's fetching something asynchronously, you'll need a thunk
Read this article on it: http://redux.js.org/docs/advanced/AsyncActions.html
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,
};
}
}