Where should I call the API ? (Thunks?, Component ? ) - javascript

I have a question about React with Redux, all concepts of redux they have me confused bit, I've watch tutorial or papers where in actions file add thunk functions for all process of call api loading, success or fail and then save result in store, my question is when I must do this or just call api in my component ? is a good practice call API in my componet ?
Thank!
Sorry, I don't speak english very well, I hope they have understood.

You have a couple of options when it comes to api calls in react/redux. Here's two that I've used:
1.Make all calls in action creator with redux-thunk:
const action = () => async dispatch => {
try {
const {data} = await axios.get('/endpoint');
dispatch({type: DO_SOMETHING, payload: data})
} catch (e) {
handleError(e)
}
}
This method works well and there's nothing wrong with it. The problem is that you end up writing a lot of boilerplate code. It also means that your action creators aren't pure functions. Pure actions are generally easier to test and reason about.
2.Use an action as a command that contains relevant api call information and a success handler that is invoked with the response. You can then write middlware that handles all your api calls in one place. This makes it easier to handle errors and keeps action creators pure. This method is a little more setup but it's worth it in the long run.
action creator that component dispatches:
const getSomthing = () => ({
type: API,
payload: {
call: {
url: "/endpoint",
method: "get"
},
success: data => ({type: DO_SOMETHING, payload: data})
}
});
middlware that handles api calls:
const api = ({ dispatch, getState }) => next => async action => {
if (action.type !== API) {
return next(action);
}
const { call, success, failure } = action.payload;
try {
const { data } = await axios(call);
if (success) {
dispatch(success(data));
}
} catch (e) {
handleError(e)
}
};
You can then apply this middleware to your store.
Boris Dinkevich uses this approach. I'll link to his talk about it which is worth a watch regardless of which method you use. https://www.youtube.com/watch?v=Gjiu7Lgdg3s

Related

Return a promise for a simple action creator in react using react-redux

I am new to the react-redux . Here , I think this is a very basic question. But , I have a action creator, and I am using a redux thunk.
export const updateActivePage = (activePage) => {
return {
type: UPDATE_ACTIVEPAGE,
payload: activePage
}
}
what I tried is
export const updateActivePage = (activePage) => (dispatch) => {
return new Promise( function(resolve, reject) {
dispatch({
type: UPDATE_ACTIVEPAGE,
payload: activePage
})
})
}
the function after then is not getting called.
Now, in my componentdidmount In want to use .then after this
So, for that I want to return a promise . So,How can I do this ? I am using the reux-thunk
Straight from redux-thunk readme:
function makeASandwichWithSecretSauce(forPerson) {
// We can invert control here by returning a function - the "thunk".
// When this function is passed to `dispatch`, the thunk middleware will intercept it,
// and call it with `dispatch` and `getState` as arguments.
// This gives the thunk function the ability to run some logic, and still interact with the store.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
);
};
}
...
// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.
store.dispatch(
makeASandwichWithSecretSauce('My partner')
).then(() => {
console.log('Done!');
});
Promises are rather used when you want to fetch some data from external source (like REST). However, if you really want to do this, a action creator has to return a function:
export const updateActivePage = (activePage) => {
return (dispatch) => {
return dispatch ({
type: UPDATE_ACTIVEPAGE,
payload: activePage
});
}
}
Something like this should return promise.
However, this promise will be resolved when action is dispatched. It's not the same as redux state change. I think that you don't really want to use promise here.
If you want to react when something in redux state changes, your component should observe the state (using mapStateToProps) and you can handle changes in componentWillUpdate method.

How to load data into a vuex module only when it's needed? problems with async/await

Is there a way to load all the data for a vuex store once but only load it if it's needed?
I assume there is but i'm struggling and i'm not sure if it's because i'm misunderstanding Vuex or Async/Await in Javascript promises.
This an example store for Roles. The userRolesApi makes an axios request and returns a promise.
import {userRolesApi} from "../api";
export default {
actions: {
setRoles(context, roles) {
context.commit('SET_ROLES', roles)
},
async loadRoles({state, dispatch}) {
if (state.all === null) {
return await userRolesApi.index().then(response => {
dispatch('setRoles', response.data)
})
}
}
},
state: {
all: null
},
getters: {
findRoleFromId: (state) => (role) => {
return _.find(state.all, {id: parseInt(role)})
},
findRoleFromName: (state) => (role) => {
return _.find(state.all, {name: role})
}
},
mutations: {
SET_ROLES (state, roles) {
state.all = roles
},
}
}
What I would like to do is call findRoleFromId from within a Vue Component.
That would then get the role from the roles array in state state.all, but also build that array from the API if it doesn't already exist.
From what I can tell it's bad practice to make api requests from inside getters so instead i've got the loadRoles method in an action instead.
But I can't call an action from a getter so now i'm going to have to call loadRoles from somewhere else, everytime I think I might need to use a role.
So I wind up with a component like this:
mounted() {
this.$store.dispatch('loadRoles')
},
computed: {
role() {
// This will be null at first but update once the api request finishes.
return this.$store.getters.findRoleFromId(this.roleId)
}
},
This actually works perfectly!
However if for some reason I call this.$store.dispatch('loadRoles') in two components in quick succession then it will make the api request twice.
I've attempted to resolve this using async/await but it doesn't seem to matter, it doesn't stop processing until the request is finished.
As a test changing my component to this:
mounted() {
this.$store.dispatch('loadRoles')
this.$store.dispatch('loadRoles')
this.$store.dispatch('loadRoles')
this.$store.dispatch('loadRoles')
},
computed: {
role() {
return this.$store.getters.findRoleFromId(this.roleId)
}
},
Causes the api request to be called 4 times instantly. Rather than waiting for the first one to finish and then on the second attempt failing the state.all === null check and not making the api request.
I've tried to be as verbose as possible in explaining what it is i'm trying to do because i'm not actually sure where it is i'm going wrong. From that my questions are:
What is the best way to populate a vuex store only when it's needed?
If my approach is a good way to do it, what's wrong with my attempt at async/await?
Your components should not care about how many times they request a resource, neither should the userRolesApi I think. If eventually you use fetch then you can cache the promise as long as it's not resolved or rejected and later requests will just return that promise.
const fetchOnce = ((cache) => (url, config) => {
const cacheKey = `${JSON.stringify(config)} ${url}`;
if (!cache[cacheKey]) {
cache[cacheKey] = axios.get(url, config)
.then((result) => {
delete cache[cacheKey];
return result;
})
.catch((error) => {
delete cache[cacheKey];
return Promise.reject(error);
});
}
return cache[cacheKey];
})({});

Asynchronous ActionCreator in React Redux

I'm pretty new in React-Redux. Was working on an application. The thing is that I faced some issues with asynchronous execution of Redux actionCreator, may be.
Below is my component. Say, I want to call an actionCreator from componentDidMount() or from an onclick event listener.
class Dashboard extends PureComponent {
componentDidMount() {
this.props.getProductsAndPackages();
let something = [];
something = this.props.products;
}
....................................
}
Or , the function this.props.getProductsAndPackages(); can be an onClick event handler that does the same thing, context is the same. I'll ask my question after first explaining my code.
At the lower side of my Dashboard container:
Dashboard.propTypes = {
getProductsAndPackages: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
.......................
};
const mapStateToProps = (state) => {
return {
.....................
products: state.products.products,
...................
};
};
const mapDispatchToProps = (dispatch) => {
return {
getProductsAndPackages: () => dispatch(getProductsAndPackagesActionCreator()),
};
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Dashboard));
My actionCreator goes like:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
const local_token = localStorage.getItem('_token');
const fullToken = 'Bearer '.concat(local_token);
axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
});
} else {
axios.get(url)
.then(response => {
dispatch(productsIsLoading(false));
if (response.data.statusCode === 200) {
dispatch(productsFetched(true));
dispatch(products(response.data.data));
} else {
dispatch(productsFetched(false));
dispatch(productsErrors(response.data.message));
}
})
.catch(error => {
console.log(error);
dispatch(productsIsLoading(false));
dispatch(productsErrors(error.message));
});
}
};
};
Now, I want my getProductsAndPackagesActionCreator() to return a Promise or anything that would allow my something variable to get the actual data returned from the server. Right now, by the time I'm getting actual data, the line something=this.props.products has already been executed and I get back the initialValue that was set for products.
I know, whenever I'll receive the populated products, component will re-render, but that does not help my decision making.
I'm using redux-thunk, by the way.
What should I do now ? Sorry for such a long post.
Actually I wanted getProductsAndPackagesActionCreator() to return a promise, which was pretty straightforward, to be honest. I figured out that if you just return the axios.get() or axios.post(), it will return a promise. So, the modified code looked like below:
export const getProductsAndPackagesActionCreator = () => {
return (dispatch) => {
dispatch(productsIsLoading(true));
let url = 'xyz';
if(!!localStorage.getItem('_token')) {
return axios.get(url, {headers: {Authorization: fullToken}})
.then(response => {
............
............
})
.catch(error => {
});
} else {
return axios.get(url)
.then(response => {
...........
...........
})
.catch(error => {
console.log(error);
});
}
};
};
And then, I could do something like below in componentDidMount() or on any onClick event:
this.props.getProductsAndPackages().then(() => {
this.setState({
...this.state,
clicked_product: this.props.product_by_id
}, () => {
//do other stuffs
});
});
Feel free to let me know if there's any issue.
I think you are close to getting what you want. First of all, you should understand that redux actions and react actions like setState are asynchronous, so you have to apply your logic keeping this in mind. I'm going to explain what i think in some points:
You have called the action creator in the correct place componentDidMount, also you can call this action in any onClick if you want.
As soon as you dispatch the action you are changing your redux state setting loading true I suppose. So now you can access this property in your render function, so you can render a Loader until your api call finishes.
When your ajax function finishes, with an error or not, I suppose you are setting loading to false and updating your products data, so you can render now your loaded products in your dashboard.
Are you sure that you have to compare your empty products array with the received data? Maybe you can check in your render function if (!this.props.products.length) return null, when you load your page you will see a loader function and later your dashboard with the products.
If you really need to compare previous products with received products componentDidUpdate is your method. In this method, you can access your previous props and compare with actual props, be careful comparing arrays, remember [] === [] is false. Maybe you can compare the length, something like
componentDidUpdate(prevProps){
if(prevProps.products.length !=== this.props.products.lenth){
doSomething()
}
}
Just to say that componentDidUpdate is executed after render, so be careful with your code to no-execute extra renderings.
Hope it helps, if you dont understand anyting just tell me :)

How do async redux functions work?

I started learning redux in reactjs. I am trying to implement an async structure to redux but I am really confused...
To implement an async function and use the promise you should type async before your function and use await before using the promise.
But in many examples I never saw they use of async before functions and await before the promise variables.
For example look at these two links:
https://redux.js.org/advanced/async-actions
https://github.com/reduxjs/redux/tree/master/examples/async
So how can I call async function in the reducer and return the async results?
For example, I want to prepare this list with an async function and get the list with axios or fetch API :
const list = [
{id: 1, title: 'One'},
{id: 2, title: 'Two'},
{id: 3, title: 'Three'}
]
export function newsReducer(state = [], action) {
switch (action.type) {
case 'GET_NEWS':
return list
default:
return state
}
}
To summarize it I would suggest, to understand this concepts action, reducer and store.
An action is a trigger for one or more reducer cases
the store is on and all the data is contained and only reduceres can change it using the method they get from the store 'dispatch'
reducer is a piece of code that produces immutable changes on the store.
So in order to do a change to the store synchronically you have to call the store method dispatch such that the reducer triggers and changes what you expect.
To do async you will have to call the reducere when the async call is done.
Example:
// action method called by react component when user submits changes
export function updateProduct(product) {
// returns a function since this method has to be called also with dispatch() in the component
return dispatch => {
// trigger first store change
dispatch({ type: PRODUCT_EDIT.PRODUCT_EDIT_REQUEST });
// trigger secod store change and mark the async call, updateProduct is ASYNC
ProductsService.updateProduct(product)
.then(
res => {
dispatch({ type: PRODUCT_EDIT.PRODUCT_EDIT_SUCCESS, data: res.data });
dispatch(successNotification('Product updated'));
},
error => {
dispatch({ type: PRODUCT_EDIT.PRODUCT_EDIT_FAILURE, data: error.response.data });
dispatch(errorNotification(error.response.data));
}
);
};
}
Other tutorials I think you should check:
https://medium.com/#rajaraodv/step-by-step-guide-to-building-react-redux-apps-using-mocks-48ca0f47f9a
One way to do it is to use redux-thunk like # Sujit.Warrier suggested in the comment here is a small and simple example that can get you started using redux-thunk.
export const getlist =()=> dispatch =>{
fetch(`api link for your list`).then(res => res.json())
.then(data => {
dispatch({type:'GET_NEWS' , payload:data})
});
};
and then you can dispatch the function in your component

React/Redux , how to "chain" actions for http call when http call is in middleware

I am setting up a basic app to call the server for some stuff. the one catch, is I have made my server calls as a piece of middleware so other devs I work with can use it. I can make the call - however I would like to add "loading", "success", and "error" actions to it. Before - I could easily do this by placing the calls in the action creator itself, something like this for example :
//in the action creator
export function fetchData() {
return dispatch => {
request
.get('/myApi')
.end((err, res) => {
if (err) {
dispatch({
type: LOADING_ERROR,
error: err
});
} else {
let myData = JSON.parse(res.text);
dispatch({
type: LIST_DATA,
myData
});
}
});
dispatch({
type: LOADING_DATA
});
};
this worked great for me for having the loading/success/error accessible on the ui (so i can do things like show/hide a loader and such).
In this new project, I have my calls in a piece of middleware like so :
//the middleware
export default (actionObject) => (store) => (next) => (action) => {
switch(action.type) {
case actionObject.LIST:
let constructedURL = actionObject.URL + "?query=&context=" + actionObject.context;
request
.get(constructedURL)
.end((err, res) => {
if (err) {
return next(action);
}
action[options.resultAction] = res.body;
return next(action);
});
break;
default:
return next(action);
}
}
So the actionObjectis just an object you pass to let you set up this middleware so it will work with your own actions/reducers, but it is just matching your defined action to make the call. Now the issue is I don't know how I can get back into the loading/success/error actions from here. I have them set up in my actions and reducers but I am unsure how to execute it in this manner.
I was first looking at something like this - https://gist.github.com/iNikNik/3c1b870f63dc0de67c38#file-helpers-js-L66-L80 , however I am not sure if this is exactly what I am trying to do. I am not sure where I can tell it to fire the action DATA_LOADING, and then in success of the call fire either LIST_DATA or LOADING_ERROR. Could use any advice, and thank you for reading!

Categories

Resources