Dispatch action on the createAsyncThunk? - reactjs

I hava a thunk action was created by createAsyncThunk. I want to dispatch an action before call api to update state.
I don't want use action getProducts.pending because I want dispatch actionLoading() for other thunk actions.
How I can i do it? Thanks!
export const getProducts = createAsyncThunk("getProducts", async () => {
// I want dispatch action actionCallAPIPending like that
// dispatch(actionLoading());
const response = await axios.get("api");
return response;
});

You can do it like this with the second param of callback function in the createAsyncThunk:
export const getProducts = createAsyncThunk("getProducts", async (_, thunkAPI) => {
thunkAPI.dispatch(actionLoading());
const response = await axios.get("api");
return response;
});

As the createStore method got deprecated I was looking for a solution to migrate an existing project to use #reduxjs/toolkit and in the same time to set up a new one.
I found #Viet's
answer very useful, although to be complete I would like to mention another way of using Async Thunks just to have it in the same thread for people who end up here and might would find it useful.
You still can create an Async Thunk without createAsyncThunk, but this way you cannot rely on the extraReducers as a downside.
export const getProducts = (): AppThunk => async (dispatch) => {
dispatch(actionLoading());
try {
const response = await axios.get("api");
// return the response or store it
return response;
} catch (error) {
// handle the error
console.warn(error)
}
dispatch(actionLoading());
};

Related

Return Data From Other Component [React Native/React]

hope you guys have a great great day! :)
I want to ask something, i need to return some list data from API,
I have 3 files, one called getUser.js and the othe is User.js and OtherUser.js, i already fetch the data from API in getUser.js, and i need to access the data from getUser.js to populate User.js and OtherUser.js
The Problem is, i can't return the data in getUser.js
here's my code in getUser.js
import React from "react";
export const getUser = async () => {
await fetch("https://myapi.com")
.then((response) => response.json())
.then((res) => {
return res;
});
};
and when i console.log inside User.js and OtherUser.js, i got this
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
}
and here's my code in User.js and OtherUser.js looks like:
import React, {useState} from 'react'
import { getUser } from './someplace/getUser'
export default function User() {
useEffect(()=>{getDataFromUser()},[])
const getDataFromUser = async() => {
console.log(getUser())
}
}
Did you guys know why? please help :)
Avoid using then chains, when using async-await. Refactor your getUser funciton to
export const getUser = async () => {
const response = await fetch("https://myapi.com");
const json = await response.json();
return json;
};
Then in your User component
console.log(await getUser())
You are logging a Promise, that's why you're seeing this in your console.
When inside async function, if you want to get the result of the function, you should not use "then".
So you could do something like this:
const getUser = async () => {
let response = await fetch("https://httpbin.org/get");
return await response.json();
};
But, notice that if fetch fails, it may throw an exception. So I recommend using a try/catch block
Also notice that in this case, when you call getUser(), you will have to use "await" before calling the function to get the json response. For example:
let user = await getUser();
In case you want to run a async function inside the useEffect hook, try doing something like this:
React.useEffect(()=>{
onOpen = async () => {
console.log(await getUser());
}
onOpen();
},[]);

How To Make A Post Request Using Redux Thunk With Redux Toolkit

I have been using Redux-Toolkit more than the React-Redux. I came across situations where I had to make GET requests, So I recently started using Redux-Thunk (before this I used useEffect but, as it's not a standard way to handle async functions, when using redux. I learned about middleware).
Here is the code of my Thunk function nad extraReducer which handles the GET request
export const fetchData = createAsyncThunk("type/getData", async () => {
try {
const response = await axios({url});
return response.data;
} catch (error) {
console.log(error.response);
}
});
export const extraReducers = {
[fetchData.pending]: (state) => {
state.loading = true;
},
[fetchData.fulfilled]: (state, action) => {
state.loading = false;
state.products = action.payload;
},
[fetchData.rejected]: (state) => {
state.loading = false;
state.error = true;
},
};
In fetchData function my returned response.data is being used in extraReducers as payload so I can set the state easily. But, now the scenario is I have make a post request and I don't know how will I send the data to my Thunk function.
First you create the action of posting data and send the data:
export const postData = createAsyncThunk(
"type/postData",
async (data) => {
try {
const response = await axios.post("https://reqres.in/api/users", data);
// If you want to get something back
return response.data;
} catch (err) {
console.error(err)
}
}
);
Then in a place where you want to send that data you just dispatch this action with the data argument that you want to send:
const handlePost = () => {
// whatever you want to send
const data = .........
dispatch(postData(data));
}
If you want, you can also modify your extraReducers for this action.

How do I load firebase data into react-redux asynchronously?

I am currently trying to load my product data into redux, but so far I cant seem to pass the product information returned from firestore into the reducer.
Index.js -> load first 10 products from firestore soon after store was created.
store.dispatch(getAllProducts)
action/index.js
import shop from '../api/shop'
const receiveProducts = products => ({
type: types.RECEIVE_PRODUCTS
products
})
const getAllProducts = () => dispatch => {
shop.getProducts(products => {
dispatch(receiveProducts)
})
}
shop.js
import fetchProducts from './firebase/fetchProducts'
export default {
getProducts: (cb) => cb(fetchProducts())
}
fetchProducts.js
const fetchProducts = async() => {
const ProductList = await firebase_product.firestore()
.collection('store_products').limit(10)
ProductList.get().then((querySnapshot) => {
const tempDoc = querySnapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() }
})
}).catch(function (error) {
console.log('Error getting Documents: ', error)
})
}
In product reducers
const byId = (state={}, action) => {
case RECEIVE_PRODUCTS:
console.log(action); <- this should be products, but it is now promise due to aysnc function return?
}
I can get the documents with no issues (tempDocs gets the first 10 documents without any issue.) but I am not able to pass the data back into my redux. If I were creating normal react app, I would add a loading state when retrieving the documents from firestore, do I need to do something similar in redux as well ?
Sorry if the code seems messy at the moment.
fetchProducts is an async function so you need to wait for its result before calling dispatch. There are a few ways you could do this, you could give fetchProducts access to dispatch via a hook or passing dispatch to fetchProducts directly.
I don't quite understand the purpose of shop.js but you also could await fetchProducts and then pass the result of that into dispatch.
A generalized routine I use to accomplish exactly this:
const ListenGenerator = (sliceName, tableName, filterArray) => {
return () => {
//returns a listener function
try {
const unsubscribe = ListenCollectionGroupQuery(
tableName,
filterArray,
(listenResults) => {
store.dispatch(
genericReduxAction(sliceName, tableName, listenResults)
);
},
(err) => {
console.log(
err + ` ListenGenerator listener ${sliceName} ${tableName} err`
);
}
);
//The unsubscribe function to be returned includes clearing
// Redux entry
const unsubscriber = () => {
//effectively a closure
unsubscribe();
store.dispatch(genericReduxAction(sliceName, tableName, null));
};
return unsubscriber;
} catch (err) {
console.log(
`failed:ListenGenerator ${sliceName} ${tableName} err: ${err}`
);
}
};
};
The ListenCollectionGroupQuery does what it sounds like; it takes a tableName, an array of filter/.where() conditions, and data/err callbacks.
The genericReduxAction pretty much just concatenates the sliceName and TableName to create an action type (my reducers de-construct action types similarly). The point is you can put the dispatch into the datacallback.
Beyond this, you simply treat Redux as Redux - subscribe, get, etc just as if the data were completely local.

react-redux re-rendering on componentDidMount

I'm using React with Redux with multiple reducers.
I have a component in which I want to fetch data from multiple reducers but each time I make a call to action it re-renders the component (obviously...)
async componentDidMount() {
await this.props.getBooksNamesAsync();
await this.props.getAuthorsNamesAsync();
await this.props.getSubscribersAsync();
this.props.setFilter(
this.props.book.bookNames,
this.props.author.authorNames,
this.props.subscriber.subscriberNames
);
}
this.props.getBooksNamesAsync() is action on book.
this.props.getAuthorsNamesAsync() is action on author.
this.props.getSubscribersAsync() is action on subscriber.
my question is what the best practice for such issue ?
Is re-rendering the component every action is legitimate ?
Should I write another action that contains all these actions in one place ?
which is quiet code duplication and I prefer to avoid it...
or any other options...
The component rerenders every time there is state change... You can and you should... Here is an example from an old project:
First action creator:
export const fetchPosts = () => async (dispatch) => {
const response = await axios.get('/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data });
};
Second action creator:
export const fetchUser = id => async dispatch => {
const response = await axios.get(`/users/${id}`);
dispatch({ type: 'FETCH_USER', payload: response.data });
};
And both combined: (note, it's making use of lodash but you do not have to...)
export const fetchPostsAnUsers = () => async (dispatch, getState) => {
await dispatch(fetchPosts());
const userIds = uniq(map(getState().posts, 'userId'));
userIds.forEach(id => dispatch(fetchUser(id)));
};
This was a use case to cut down on the number of calls made to the api but the same holds true for your use case...

Async/await redux thunk not returning promise to action correctly

I have a thunk using Axios that's posting to an Express route using Sequelize.
The route is posting correctly (ie. data is getting added to the db) but the action inside of the React component isn't behaving as expected. Using async/await, I expect the action to wait until it completes the db post before continuing but that's not the case here. I'm getting undefined from the action.
The thunk hits the express route where I'm dispatching the action to update my redux store and returning the response:
const addedNewList = (newList) => ({type: ADD_NEW_LIST, newList})
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
dispatch(addedNewList(data))
return data
} catch (err) {
console.error(err)
}
}
Using debugger, I can confirm that return data is in fact returning the response from the server that I need. I can also confirm that the redux store is getting updated correctly.
But here, when I try and access that response data as result, I get undefined:
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
// ** result is 'undefined' **
this.handleClose()
// pass off the results
}
If I add a setTimeout after I evoke the addNewList action, it works as expected. This suggests to me that maybe it's not returning a promise? But my understanding was that if you returned the response from the server in the thunk, it would do that.
For completeness, here is my route which I've also confirmed with debugger that data is being passed as expected:
const userAuth = function(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).send('Unauthorized user')
}
router.post('/add', userAuth, async (req, res, next) => {
const { name, userId } = req.body
try {
const list = await List.create({ name, userId })
res.json(list)
} catch(err) { next(err) }
})
Why is the action returning undefined in the handleSubmit method?
Try returning the dispatch of addedNewList(data) instead:
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
return Promise.resolve(dispatch(addedNewList(data)));
} catch (err) {
console.error(err)
}
}
That being said, you could consider restructuring the component to instead utilize mapStateToProps to use values/result from the updated Redux store rather than explicitly awaiting the response and manually passing the value?
The response from Alexander got me on the right track so I'm sharing my solution in case it helps someone (as he suggested).
While I could have continued to try and solve this by wrapping the dispatch in a Promise, the better solution was to rethink how the component was structured.
In my situation, I wanted to get the ID for the newly created row in the database so that I could pass it into history.push.
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
history.push(`/list/${result.id}`)
}
With result coming back undefined, the url was not updating correctly.
The better solution was to access the new data from the redux store where it was updated. This way I could be certain the history wouldn't get updated until the data was ready.
So my updated component now looked something like this where the history wouldn't update until a newId was available:
handleSubmit = () => {
this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
}
render(){
const { newId } = this.props
if (newId) {
history.push(`/list/${newId}`)
}
return (
....
)
}
}
const mapStateToProps = (state) => {
return {
newId: state.list.newId
}
}
Instead of putting this into render, I could probably also use a component lifecylcle method like componentWillReceiveProps or similar.

Resources