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
Related
I have a React app built with the Minimal template and I'm trying to follow along with one of their tutorials, in order to create a Redux slice that feeds some data to a custom component. The data itself is collected from Firebase. Below is my code:
firebase.js - helper
export function getDocuments(col) {
const colRef = collection(db, col);
const q = query(colRef, where('uid', '==', auth.currentUser.uid));
getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
// return [1,2,3]
}
product.js - Redux slice
export function getProducts() {
return async (dispatch) => {
dispatch(slice.actions.startLoading());
try {
const products = await getDocuments('products');
dispatch(slice.actions.getProductsSuccess(products));
} catch (error) {
dispatch(slice.actions.hasError(error));
}
};
}
ProductList.js - component
const dispatch = useDispatch();
const { products } = useSelector((state) => state.client);
useEffect(() => {
dispatch(getProducts());
}, [dispatch]);
useEffect(() => {
if (products.length) {
// setTableData(products);
}
}, [products]);
If I console log data in the helper function (firebase.js), I get the values I expect, once the promise is resolved/fulfilled. However, if I console.log clients in the product.js slice or later in the component, I get undefined.
I assume my problem is not being able to understand how async + await + useEffect work together in order to fix this. My assumption is that I am trying to access the value before the promise is resolved and therefore before the helper function returns it. I confirmed that by returning a simple array [1, 2, 3] in my helper function as a test.
I think I am missing something fundamental here (I am not very experienced with React and JS in general and still learning things on the go). Can someone help me understand what am I doing wrong?
Thank you!
With await you can await the fulfillment or rejection of a promise, but your getDocuments Function does not return a promise. Change the last line of the function to the following:
return getDocs(q).then((snap) => {
const data = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
return data;
});
Async and Await are no different in React than in plain JavaScript:
When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method
useEffect():
By using this Hook, you tell React that your component needs to do something after rendering. This function will run every time the component is re-rendered.
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
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.
In my React Native app, I have an action creator that dispatches an action to update the state in the Redux store. I call this action creator with 'await' and then do a setState(), but I noticed the Redux store is getting updated AFTER the setState is completed. My code looks like this:
updateFunction = async () => {
await this.props.actionCreator1();
this.setState({property1: this.props.property1});
}
I 'await'ed the action dispatch because I need the store to be updated before I setState(). I must be misunderstanding what gets returned etc when you dispatch an action, because the store does not get updated until after setState() runs. Can anyone explain why this could occur?
Thanks!
UPDATE
This is how I bind my Redux store to my component
const Component1 = connect(
state => ({
property1: state.Reducer1.property1
}),
dispatch => bindActionCreators({
actionCreator1: ActionCreators.actionCreator1
}, dispatch)
)(Component1)
This is where I create the action creator in actions.js
export const ActionCreators = {
actionCreator1: () => ({type: 'actionCreator1', property1: 'value'})
}
This is where I reduce the actions in reducer.js
const initialState = {
property1: undefined
}
export default (state = initialState, action) {
switch(action.type) {
case('actionCreator1'): {
return {
...state,
property1: action.property1
}
}
}
}
You are not supposed to use await on a function that doesn't return a Promise, to make it work either you can wrap your action creator so that it returns a promise then resolves to the value you want, or do a different approach not relying on async-await.
You can define your action creator like below:
export const ActionCreators = {
actionCreator1: () => new Promise(function (resolve, reject) {
return resolve({type: 'actionCreator1', property1: 'value'})
});
}
It will work if you added redux-thunk middle ware.
I'm not sure you are using React/Redux best practices. You are using action which changes the redux state but at the same time you are trying to use setState which updates the state of React. Moreover, your code does not execute how you expect it because your await function is not promise based.
This does implicitly responds to your question. Take the functions below as an example:
function something1(){
setTimeout(() => console.log('before'), 2000);
}
function something2(){
var result = new Promise(resolve => setTimeout(() => resolve(console.log('before')) ,2000));
return result;
}
function el(){
console.log('after')
}
updateFunction = async () => {
await something1(); // await something2();
el();
return 'done';
};
and try first to run updateFunction() in your console. Because function something1() is not promise based you will see that it will not await and the string 'after' will be printed first. Now inside your updateFunction() try to change await something1() with something2() in your console again. You will see that the rest of the el() function will await for function something2() to finish first and your console will print out the strings 'before' and then 'after'. Hope this clarifies things a bit.
I have redux-thunk set up in my react-native code.
In mapDispatchToProps I have:
const mapDispatchToProps = dispatch => {
return {
login: (state, navigator) => dispatch({
type: "USER",
payload: loginFunc(state, navigator)
}),
}'
}
The login function is a firebase function that returns a promise chain that eventually returns the object I need.
However, in my reducer it looks like my payload is a promise or some funky object.
It looks like:
{
a: someVal,
b: sommeOtherVal,
c: someMoreVal,
...ValsUpToI.
i: {
// The values I actually need show up
}
This messes me up because when I try to do payload.myValue I get undefined in my redux state. I don't think it is safe to just do payload.i either because this is probably some promise or async issue right? That i could very well be something else later?
Use asynchronous actions approach to execute promise based function API calls as mentioned in redux documentation.
const mapDispatchToProps = dispatch => {
return {
login: (state, navigator) => {
return loginFunc(state, navigator).then(
response => dispatch({
type: "USER",
payload: response
}),
error => console.log('An error occurred.', error)
)
}
}
That way you can call promise based function and utilize response or error correctly.