Dispatch is not defined-ReatcJs - reactjs

I tried looking for answers from the sources that had same issue, but didnt work for my case.
I'm using this function to get some settings for my application:
js:
export function _someFun() {
setData("abc").then(res => {
dispatch({ type: ACTION.SET_USER_SUCCESS, res });
}, (error) => {
dispatch({ type: ACTION.SET_USER_FAILURE, error });
});
}
where: setData is a function I'm importing from the Api file that basically sends a response to fetch the data. With the above call, its going into the success call but saying: "dispatch is not defined". Not sure how to get this working..any idea???

You need to install first the redux-thunk middleware:
npm install --save redux-thunk
Then, import an actionTypes object from the actionTypes.js file, that stores all the action types. Basically instead of ACTION you should use actionTypes. Is more explicit and clear that way.
And then try this in your action creator file:
export const setUserSuccess = (response) => {
return {
type: actionTypes.SET_USER_SUCCESS,
payload: {
response: response
}
}
};
export const setUserFailure = (error) => {
return {
type: actionTypes.SET_USER_FAILURE,
payload: {
error: error
}
}
};
export const _someFun = () => {
// Using the redux-thunk middleware
return dispatch => {
setData("abc")
.then(response => {
dispatch(setUserSuccess(response));
}).catch(error => {
dispatch(setUserFailure(error));
});
}
};

Related

Axios calls with React : best practises

i want to know if there is some clean code or update to make it on my code, because i think i repeat the same code on every actions on my redux, my question is how can I avoid calling axios on my actions files ?
Please take a look on my code here :
export const SignInType = (host, lang) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_SIGNINTYPE_REQUEST,
});
const { data } = await axios.get(
`/${lang}/data?host=${host}`
);
console.log({ data });
dispatch({
type: USER_LOGIN_SIGNINTYPE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_CLEAR_ERROR,
});
} catch (err) {
dispatch({
type: USER_LOGIN_SIGNINTYPE_FAIL,
payload: err,
});
}
};
I Really want to delete the Axios name from my actions file and make it on a separate file, but how can i do this ?
Thank you
We can suggest but there's no correct answer to this, initially any redundant lines of code can be abstracted, so in order to make things a little bit easier, we need to abstract the obvious and add the meaningful, e.g:
abstract the way you write action creators:
const actionComposer = (options) => (...args) => async dispatch => {
const modifiedDispatch = (type, payload) => dispatch({ type, payload });
const { action, onSuccess, onFailed } = options(modifiedDispatch);
try {
if (action) {
const res = await action(...args)
onSuccess(res);
}
} catch (err) {
onFailed(err)
}
}
then your code can look like this:
export const SignInType = actionComposer((dispatch)=> {
return {
action: async (host, lang) => {
dispatch(USER_LOGIN_SIGNINTYPE_REQUEST);
const { data } = await axios.get(`/${lang}/data?host=${host}`);
return data;
},
onSuccess: (res) => {
dispatch(USER_LOGIN_SIGNINTYPE_SUCCESS, data);
dispatch(USER_LOGIN_CLEAR_ERROR);
},
onFailed: (err) => {
dispatch(USER_LOGIN_CLEAR_ERROR, err.message)
}
}
})
Redux Toolkit already has a createAsyncThunk API that does all the work of defining the action types and dispatching them for you. You should use that.
Alternately, you can use our RTK Query data fetching and caching library, which will eliminate the need to write any data fetching logic yourself.

Should I handle errors in my action creators

In the following context how should I handle possible errors:
export async function testAction(data) {
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
return {
type: 'TEST_ACTION',
payload: response
}
} catch(err) {
// ???
}
}
// Somewhere in a component:
<Button onClick={ () => dispatch( testAction() ) }>
Test Stuff
</Button>
Or is better to actually dispatch from the component, eg:
refactor action creator:
export async function testAction(data) {
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
return response
} catch(err) {
return err
}
}
Somewhere in a component:
const handleTestAction = () => {
testAction().then(r => dispatch( { type: 'TEST_ACTION', payload: r } ) ).catch( // hadnle errors)
}
<Button onClick={ handleTestAction }>
Test Stuff
</Button>
I know the redux style guide recommends using Action Creators for dispatching actions but in this particular case I am calling the action first and then use dispatch. How should I approach it?
You can create another reducer to handle errors.
export async function testAction(data) {
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
return {
type: 'TEST_ACTION',
payload: response
}
} catch(err) {
return {
type: 'ERROR',
payload: err
}
}
}
But you cannot do it like above. because the process is asynchronous
You have to use a 'redux-thunk' for that. Once you add it as a middle-ware to your store, you can get the dispatcher in to your action creater, so you can dispatch anything in the test action after you complete.
So your reducer should change to the below one,
export async function testAction(data) {
return (dispatch) => {
try {
let response = await
axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
dispatch({
type: 'TEST_ACTION',
payload: response
})
} catch(err) {
dispatch({
type: 'ERR',
payload: response
})
}
}
}
UPDATE
Once you connect the middleware, you can use dispatch in the action creater,
const store = createStore(
reducers,
{},
applyMiddleware(thunk)
);
You need to only add the thunk to the store just like above.
You can make it more clear by refactor your code like below
export const testAction = () => async (dispatch) => {
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
dispatch({
type: 'TEST_ACTION',
payload: response
})
} catch(err) {
dispatch({
type: 'ERR',
payload: response
})
}
}
If your API is going to change in dev and prod modes, You can use below way,
Somewhere in your application,
const axiosInstatnce = axios.create({
baseURL: "https://jsonplaceholder.typicode.com",
headers: {/* you can set any header here */}
});
Now when you create store,
const store = createStore(
reducers,
{},
applyMiddleware(thunk.withExtraArgument(axiosInstatnce))
);
Now you can get the axiosInstance as the third argument of the function you return from the testAction. 2nd argument gives the current state.
export const testAction = () => async (dispatch, state, api) => {
try {
let response = await api.get(`/todos/1`);
dispatch({
type: 'TEST_ACTION',
payload: response
})
} catch(err) {
dispatch({
type: 'ERR',
payload: response
})
}
}
Now in your component,
import {testAction} from '../path/to/actions'
const dispatch = useDispatch()
dispatch(testAction())
If you want to write async code in an action creator, you need to write an async action creator. Regular action creators return an object whereas async action creators return a function instead of an object.
export function testAction(data) {
return async function(dispatch) {
// async code
}
}
Inside the function returned by an async action creator, you have access to dispatch which can be used to dispatch any success action in case of successful response from server and in case of error, you can dispatch an action indicating that an error has occurred.
export function testAction(data) {
return async function (dispatch) {
try {
let response = await axios.get(`https://jsonplaceholder.typicode.com/todos/1`);
dispatch({
type: 'TEST_ACTION',
payload: response
});
} catch(err) {
dispatch({type: 'TEST_ACTION_ERROR', message: 'error occurred'});
}
}
}
You also need to use redux-thunk middleware if you have async action creators in your code. This middleware allows action creators to return a function.
For complete details about how to create async action creators and how to setup redux-thunk middleware to make async creators work, take a look at Async Actions

How can I mock multiple get axios request in Jest unit test for an async action that calls a few more async actions?

I'm using axios mock for testing redux actions in my app. I stuck with the problem of testing an async action function that sends POST request and dispatches a few more async actions that send their own GET requests. How can I manage that using mocks?
Actions:
export function recordStepCompleted(step, isNewLevel = true) {
return function (dispatch) {
// setUserTaskComplete() action sends `POST` request
dispatch(setUserTaskComplete(step))
.then(() => isNewLevel && dispatch(rewardsActions.getUserLevels()));
dispatch({
type: types.RECORD_STEP_COMPLETED,
payload: { step }
});
};
}
export function getUserLevels() {
return function (dispatch, getState) {
dispatch(getAchievements()) // it sends GET request
.then(achievements => {
dispatch({ type: types.GET_LEVELS.REQUEST });
return api.getLevels() // it also sends GET request
.then(res => {
//......
dispatch({
type: types.GET_LEVELS.SUCCESS,
payload: { levels: { ...res.data, ... } }
});
})
.catch(error => /*....*/);
});
};
}
So, in total, I need to mock three different requests to test the recordStepCompleted action but I don't know how. Please, help me with that. Thanks in advance!
You can extend AxiosStatic Interface to create your own and use jest.fn() to
pass your responses:
import axios, { AxiosStatic } from "axios";
jest.mock("axios");
interface AxiosMock extends AxiosStatic {
mockResolvedValue: (promise: Promise<Partial<AxiosResponse>>) => void;
mockRejectedValue: (error: Partial<AxiosError>) => void;
mockResolvedValueOnce: (promise: Promise<Partial<AxiosResponse>>) => this;
}
const mockAxios = axios as AxiosMock;
const myMock = jest.fn();
myMock
.mockReturnValueOnce({ "Your DATA 1" })
.mockReturnValueOnce({ "Your DATA 2" })
.mockReturnValueOnce({ "Your DATA 3" });
mockAxios
.mockResolvedValueOnce(Promise.resolve(myMock()))
.mockResolvedValueOnce(Promise.resolve(myMock()))
.mockResolvedValue(Promise.resolve(myMock()));

Of Redux Thunk, Sequelize (Bluebird) - returning promise to action creator "chain"

So similar to some previous posts referenced below, I'm trying to chain dispatch through Thunk, however my difficulty is on the return from Sequelize. I can see the MySQL query hit the DB and return data, however that return is not flowing through the action-creator to the subsequent .then
I presume it's the manner in which I'm trying to use Sequelize - I'm new to it.
Many thanks!
Code:
initDB.js
...
export function sequelizeQry(qryName: string) {
let query;
// For later query mapping
switch (qryName) {
case 'POSummary':
query = qry.spPOLedgerCardSummaryALL;
break;
default:
query = qry.spPOLedgerCardSummaryALL;
}
return new sequelize.Promise(
() => sequelize.query(query, { type: sequelize.QueryTypes.RAW })
.then((response) => {
console.log('Returning promise: ', response); //<-- This hits the console with data
return response;
})
);
}
database-actions.js
// #flow
import { fetchingQuery } from '../action-creators/database-creators';
const fetchQuery = (qryName: string) =>
(dispatch: *) => dispatch(fetchingQuery(qryName));
export default fetchQuery;
database-creators.js
// #flow
// Action Types
import { FETCH_QUERY } from '../constants/ActionTypes';
import { sequelizeQry } from '../utils/initDB';
/** Action-creators */
export function fetchingQuery(qryName: string) {
const actionType = FETCH_QUERY;
return (dispatch: *) => {
dispatch({ type: `${actionType}_PENDING` });
sequelizeQry(qryName) //<-- This gets called
.then((payload) => dispatch({ //<-- Don't seem to get here
type: `${actionType}_FULFILLED`,
payload
}))
.catch(err =>
// Dispatch the error action with error information
dispatch({
type: `${actionType}_REJECTED`,
error: err
})
);
};
}
Some other references I've checked:
Redux thunk: return promise from dispatched action
return promise from store after redux thunk dispatch
All credit goes to adrice727.
Here's the code change for future visitors:
...
return new sequelize.Promise(
() => sequelize.query(query, { type: sequelize.QueryTypes.RAW })
.then((response) => {
console.log('Returning promise: ', response); //<-- This hits the console with data
return response;
})
);
...
// BECOMES
return new sequelize.Promise(
(resolve) => sequelize.query(query, { type: sequelize.QueryTypes.RAW })
.then((response) => {
console.log('Returning promise: ', response);
return resolve(response);
})
);

render view after a post request in react/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

Resources