Hi I'm new to the react/redux development environment so I appreciate any help.
I'm trying to make 2 API calls asynchronously when componentDidMount by calling dispatch(fetchAllPositions(selectedUserDivision)) in my app.js
I found a suggested method in this post and the fetchAllPositions function wraps two action functions together in a Promise.all
export function fetchAllPositions(division) {
return dispatch => Promise.all([
dispatch(fetchUserPositionsIfNeeded(division)),
dispatch(fetchDefaultPositionsIfNeeded(division))
])
.then(console.log("fetched both"))
}
The two action functions are nearly identical, they just call a slightly different API url. One of them looks like the follows, where the shouldFetchUserPosition is just a pure function that returns a boolean:
function fetchUserPositions(division) {
return dispatch => {
const url = apiOptions.server + `/api/user/position?division=${division}`
dispatch(requestUserPositions(division))
return fetch(url, options)
.then(response => response.json())
.then(json => dispatch(receiveUserPositions(division,json)),
err => console.log(err))
}
}
export function fetchUserPositionsIfNeeded(division) {
return (dispatch, getState) => {
if (shouldFetchUserPositions(getState(), division)) {
return dispatch(fetchUserPositions(division))
} else {
return Promise.resolve()
}
}
}
The logic is that for an update, a REQUEST... action is sent while pulling data, then a RECEIVE... action is sent when data is ready to be updated into the new state.
The trouble is that the Promise.all should wait for REQUEST1 REQUEST2 RECEIVE1 RECEIVE2 to all come in before doing the .then(console.log("fetched both"))
Right now, it's does the .then after first 2 REQUEST are finished, not waiting for the 2 RECEIVE to come in.
I suspect it's the nested nature of the requestUserPositions() within the function that wraps fetch()
The REQUEST action is a simple function, and in the reducer it just flips an isFetching property to true:
function requestUserPositions(division) {
return {
type: REQUEST_USER_POSITIONS,
division
}
}
Sorry for this long issue but I'd appreciate any suggestions.
This is a careless mistake
Turns out that when the .then() is wrapped inside a dispatch it gets executed simultaneously as the Promise.all() is being carried out.
The intended dispatch order was created by tagging the then(()=>console.log to the last dispatch dispatch(fetchAllPositions(selectedUserDivision)) done from ComponentDidMount
Related
I'm trying to improve my code by trying to use redux thunk as well as possible but I've searched a lot, no solution corresponds to my research.
Basically, with the twitch API we have to make 2 API calls, one to get the authentication key and another to get the data from this key.
Here is my current code:
dispatch(getOauthKey())
}, [])
useEffect(() => {
if(refresh){
dispatch(getAllStreams(oAuthKey.access_token))
}
}, [oAuthKey.access_token])
Here I had to use a true/false with refresh to know when to dispatch or not and [oAuthkey.access_token] to restart the useEffect when the variable receives data.
It works but it's not optimized at all and I know you can chain dispatches with thunk but I couldn't do it. Here is what I tried to do:
const thunkA = () => (dispatch, getState) => {
dispatch(getOauthKey())
}
const thunkB = (key) =>(dispatch, getState) => {
dispatch(getAllStreams(key))
}
console.log(oAuthKey.access_token)
useEffect(()=> {
dispatch(thunkA()).then(() =>dispatch(thunkB(oAuthKey.access_token))
);
}, [])
And I have this as an error:
TypeError: Cannot read properties of undefined (reading 'then'). I specify that the first call api sends the key into the store so the oAuthKey comes from the latter
Sorry I'm really new to reactjs / redux / thunk and the problem doesn't jump out at me here haha.
thank a lot in advance
There are several options to do that. as a standard redux-thunk solution, you can use promise changing for dispatching the actions in the thunk.
First of all, you need to add this package: redux-promise-middleware with the official documentation.
Given a single action with an async payload, the middleware transforms the action to separate pending action and a separate fulfilled/rejected action, representing the states of the async action.
Now you can chain your actions:
const chainMyActions = () => {
return (dispatch) => {
const response = dispatch(thunkA());
response.then((data) => {
dispatch(thunkB(data.access_token))
})
}
}
Now you can call the sample chainMyActions function in your useEffect.
I've seen a number of posts covering similar chaining questions, but I'm trying to understand what I'm specifically missing.
Ultimately, I want to make an API call, then make a second based on the results of the first. To do so, of course, I need to have the results, not just a promise to get them before I make the chained call. I started from an example that does not have the chaining dependency:
export const FetchUserSync = () => {
return Promise.all([
fetchUsers(),
fetchPosts()
]).then(([user, posts]) => {
return { user, posts };
});
function fetchUsers()...
function fetchPosts()...
}
When I call it, the data comes back and state values are set.
useEffect(() => {
dataPromise.then(data => {
setUser(data.user);
setPosts(data.posts);
});
Attempting to change the first code so the second call will wait for the first to complete, I get results in the function (console.log or setting debbugger shows populated objects for user and posts exist), but the useEffect's calling function shows the result to be undefined.
export const FetchUserSync = () => {
return Promise.all([fetchUsers()])
.then(([user]) => {
Promise.all([user, fetchPosts()])
.then(([user, posts]) => {
return { user, posts }
})
});
function fetchUsers()...
function fetchPosts()...
}
Any suggestions on how to make the second idea work or should I just scrap it and go with one of the other answers?
In this case, you wouldn't want to use Promise.all.
Since both fetchUsers and fetchPosts both return promises already, wrapping singular calls with Promises for fetchUser and fetchPosts is an anti-pattern.
Additionally, consider using async/await since they avoid deeply nested chains of data.
Here's what you could do:
export const FetchUserSync = async () => {
const users = await fetchUsers();
const posts = await fetchPosts();
function fetchUsers() { ... }
function fetchPosts() { ... }
return { users, posts }
}
The code will make a request for users, then once users is received, it will make another call for posts.
Consider reading more on async/await: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
In a component I have a button that onClick dispatches a deleteQuestion action that sends a fetch backend delete request, and when the response is received is supposed to call another action to update the Redux store.
However, since it's an onClick event, the deleteQuestion thunk function does not work like a traditional dispatch request made from ComponentWillMount and instead returns an anonymous function with a dispatch parameter that never is called. Therefore, I'm required to call the dispatch twice simultaneously in the onClick method like so:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
deleteQuestion(questionId, history)(deleteQuestion); //calling method twice
}
While this approach is effective for trigging the delete request to the Rails backend, when I receive the response, the second dispatch function that I have embedded in the deleteQuestion action -- dispatch(removeQuestion(questionId)) -- won't trigger to update the Redux store. I've tried placing multiple debuggers in the store and checking console and terminal for errors, but nothing occurs.
I've read through the Redux docs and other resources online and from what I've been able to find they all say it should be possible to include a second dispatch call in a .then request. While it's possible to do this in get, post, and patch requests, I can't figure out why it won't work in a delete request.
The thunk call I make is:
export function deleteQuestion(questionId, routerHistory) {
return (dispatch) => {
fetch(`${API_URL}/questions/${questionId}`, {
method: 'DELETE',
}).then(res => {
dispatch(removeQuestion(questionId))
})
}
}
And the github is:
https://github.com/jwolfe890/react_project1/blob/master/stumped-app-client/src/actions/questions.js
I'd really appreciate any insight, as I've been trying to get passed this for two days now!
You are calling the action deleteQuestion directly instead of having your store dispatch the delete question action for you. You should instead call the deleteQuestion from your props that is already mapped to dispatch:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
this.props.deleteQuestion(questionId, history);
}
If you pass in an object as mapDispatchToProps each element is dispatch call. In other words your mapDispatchToProps is equivalent to:
(dispatch) => ({
deleteQuestion: (...params) => dispatch(deleteQuestion(...params))
})
I'm having a problem with controlling the execution of my fetch() functions. Particularly I want to avoid letting the user spam fetch() requests.
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
So my question is, when exactly does a fetch() already get executed?
If it matters, then my code looks roughly like this.
Parent action:
{
return (dispatch) => {
if (mode === 'MY') {
dispatch(myAction(...);
}
dispatch(someOtherAction(...));
}
}
My action:
{
type: 'TYPE',
promise: post(url, payload)
}
My post method:
{
console.log('sending POST');
return fetch(url, {
//fetch info
});
}
My middleware:
{
return next => action => {
const { promise, //other fields } = action;
//Already a promise here.
if (!promise) {
return next(action);
}
return promise.then(
//Processing data
);
};
}
My idea was to do this inside of middleware, but by the time the action with a fetch() gets there the payload is already a promise.
To solve this problem, I would do it as a check inside the action. So when you fire the fetch request, dispatch an action that a fetch is currently executing, update the state to say fetching = true. Then in any other action that needs to use fetch, check against that state and return nothing if it's true.
To answer your comment:
When you call an action creator, that is called immediately of course. When you call the dispatch, that will then execute the function (if using redux-thunk). Now, since redux-thunk is a middleware, it executes depending on order that you attached the middleware. So if you put your own middleware in the setup before redux thunk, it will execute before it.
const store = createStore(
combineReducers(reducers),
applyMiddleware(yourMiddleware, ReduxThunk)
);
Otherwise you have the understanding correct. An action fires immediately after calling dispatch, it'll go through the middleware in order, and then pass the action info to your reducers.
I am trying to chain multiple actions together in the following fashion:
A. post user data to database
B. use posted data to query Elasticsearch for results
(I do A and B in parallel)
B1. with results from ES, query original database for results from two tables
B2. navigate to new page and update UI
I am using thunks right now to reason about my code, but I also found this async pattern to be extremely verbose:
export function fetchRecipes(request) {
return function(dispatch) {
dispatch(requestRecipes(request))
return fetch(url)
.then(response => response.json())
.then(json => dispatch(receiveRecipes(request, json))
)
}
}
this, along with "requestRecipes" and "receiveRecipes" as other action creators seems like quite a bit just to make one async call. (a request, a receive, and a fetch function)
summary: when you're chaining 2-3 async actions whose outputs depend on each other (I need to promisify when possible), is there a more efficient means of doing so without writing 3 functions for each async call?
I figure there had to be a way. I'm pattern matching off of the Redux docs and soon became very overwhelmed with the functions I was creating
thanks a lot for the feedback!
You can use redux-saga instead of redux-thunk to achieve this more easily. redux-saga lets you describe your work using generators and is easier to reason about.
The first step is to describe how you pass your data to redux without worrying about services or async stuff.
Actions
// actions.js
function createRequestTypes(base) {
return {
REQUEST: base + "_REQUEST",
SUCCESS: base + "_SUCCESS",
FAILURE: base + "_FAILURE",
}
}
// Create lifecycle types on `RECIPES`
export const RECIPES = createRequestTypes("RECIPES")
// Create related actions
export const recipes = {
// Notify the intent to fetch recipes
request: request => ({type: RECIPES.REQUEST, request})
// Send the response
success: response => ({type: RECIPES.SUCCESS, response})
// Send the error
error: error => ({type: RECIPES.FAILURE, error})
}
Reducer
// reducer.js
import * as actions from "./actions"
// This reducer handles all recipes
export default (state = [], action) => {
switch (action.type) {
case actions.RECIPES.SUCCESS:
// Replace current state
return [...action.response]
case actions.RECIPES.FAILURE:
// Clear state on error
return []
default:
return state
}
}
Services
We also need the recipes API. When using redux-saga the simplest way to declare a service is to creating a (pure) function which reads the request as argument and returns a Promise.
// api.js
const url = "https://YOUR_ENPOINT";
export function fetchRecipes(request) {
return fetch(url).then(response => response.json())
}
Now we need to wire actions and services. This is where redux-saga come in play.
// saga.js
import {call, fork, put, take} from "redux-saga/effects"
import * as actions from "./actions"
import * as api from "./api"
function* watchFetchRecipes() {
while (true) {
// Wait for `RECIPES.REQUEST` actions and extract the `request` payload
const {request} = yield take(actions.RECIPES.REQUEST)
try {
// Fetch the recipes
const recipes = yield call(api.fetchRecipes(request))
// Send a new action to notify the UI
yield put(actions.fetchRecipes.success(recipes))
} catch (e) {
// Notify the UI that something went wrong
yield put(actions.fetchRecipes.error(e))
}
}
}
function* rootSaga() {
yield [
fork(watchFetchRecipes)
]
}
And that's it! Whenever a component will send a RECIPES.REQUEST action, the saga will hook up and handle the async workflow.
dispatch(recipes.request(req))
What's awesome with redux-saga is that you can easily chain async effects and dispatch actions during the workflow.
Based on your description, the only time you actually update your UI is right at the end of all these asynchronous operations (B1).
If you don't use the results from the preceding async calls to change your application state / update your UI, what is the benefit of having these fine-grained actions?
Of course there are things like "loading / request started" and "finished loading / request stopped", but it seems to me, that in your case, you could just do the chained async calls outside of redux (in some kind of API-layer) and only use one action.
This action dispatches a "REQUEST_STARTED", then calls the API-layer, which does the DB-calls and elasticsearch request etc., and then dispatches either "REQUEST_SUCCESS" or "REQUEST_FAILURE", based on the result of the promise, which will give you the data you need to update your UI.
This way, the state in redux only concerns itself with ONE side-effect, instead of the implementation details of your chained calls. Also, your action gets a lot simpler, because it just handles the results of one async call.