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.
Related
I am just writing up a a small site that fetches list of repositories for a given user and displays them in a grid.
To achieve it i am using probably an overkill combination of redux, redux-sagas, axios and redux-hook.
First thing i do is i have a httpClient that does the async call to fetch the repos which returns array of objects [{},{},{}]
import axios from "axios";
export const getProjects = async () => {
return await axios.get("https://api.github.com/users/xxx/repos", {
headers: {
"Content-type": "application/json",
},
});
};
inside my Container component which is loaded when the app mounts i dispatch a action to trigger the redux cycle:
const dispatch = useDispatch();
useEffect(() => {
dispatch(getProjectsRequest());
}, []);
the Action:
export const getProjectsRequest = () => ({
type: ActionTypes.GET_PROJECTS_REQUEST,
})
This is then captured by my saga where i yield my httpCLient that return the array of objects and passes the payload onto getProjectsSuccess(result.data) which is :
export const getProjectsSuccess = (projects) => ({
type: ActionTypes.GET_PROJECTS_SUCCESS,
payload: {
projects
}
})
SAGA:
import { call, put, takeEvery, fork } from "redux-saga/effects";
import { ActionTypes } from "../actionTypes";
import * as actionProjects from "../actions/projectsAction";
import * as http from "../../api/httpClient";
// Worker Saga
function* fetchProjects() {
try {
const result = yield call(http.getProjects);
yield put(actionProjects.getProjectsSuccess(result.data));
} catch (error) {
console.log(error);
yield put({ type: "GET_PROJECTS_FAILED", message: error.message });
}
}
function* watchGetProjectsRequest() {
yield takeEvery(ActionTypes.GET_PROJECTS_REQUEST, fetchProjects);
}
const projectsSagaResult = [fork(watchGetProjectsRequest)];
export default projectsSagaResult;
This is the captured in my reducer and updates the state accordingly with array of objects:
case ActionTypes.GET_PROJECTS_SUCCESS: {
return {
isLoading: false,
...action.payload,
};
}
FINALLY:
In my projects.js component where i am trying do loop and display all the projects from GITHUB user i use const { projects } = useSelector((state) => state.gitHubPortfolio)
so that i can access the state slice and filter over it like so:
const test = projects.filter(x => {return x.name === "m" })
This instantly throws a error:
Uncaught TypeError: Cannot read properties of undefined (reading 'filter')
But when i step through the code in the browser i can do this without the error so the useSelector fetches array of objects from the state.
Now i the console i can simply filter projects array whilst inn debugger mode like so:
AT LAST
I have no idea why i cant filter through the projects array inn my code, but it seems to me like its some PROMISE issue it might be that the projects are not set before i am trying to filter them i really have no idea.
I am using react stripe in my project. I followd the tutorial https://stripe.com/docs/recipes/elements-react.
As mentioned in the document, the form is exported as follows.
export default injectStripe(Form)
In the documentation the api call is made as follows.
async submit(ev) {
let {token} = await this.props.stripe.createToken({name: "Name"});
let response = await fetch("/charge", {
method: "POST",
headers: {"Content-Type": "text/plain"},
body: token.id
});
if (response.ok) console.log("Purchase Complete!")
}
But i need to connect the redux for making the submit api call.
checkoutActions.js
import * as types from '../constants/actionTypes';
export function checkout(obj) {
const api = types.API_URL_CHECKOUT;
return dispatch => {
return dispatch({
type: types.ACTION_CHECKOUT,
promise: client => client.post(api, obj).then((data) => {
return data;
}),
});
};
}
So i have modified the form export as follows.
export default connect(state => ({
...state.resp
}),{
...checkoutActions
})injectStripe(Form)
But it is returning the error
Parsing error: Unexpected token, expected ";"
Any idea on how to connect redux in stripe checkout form?
You are missing parenthesis over injectStripe.
export default connect(state => ({...state.resp}),{...checkoutActions })(injectStripe(Form))
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.
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 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