Chaining Redux actions - javascript

I am new to React, Redux and JS overall. I want to know how can I dispatch and action after another is finished - Promises in correct way. My code actually works but it keeps throwing error:
readingActions.js?14b9:56 Uncaught (in promise) TypeError: dispatch(...).then is not a function(…)
This is my setup.
This is my action creator what I want chained action and where warning happends.
export function createReading(reading) {
return function (dispatch) {
dispatch({type: CREATE_READING});
return request(
`${API_URL}new`, {method: 'POST', body:JSON.stringify(reading)},
(json) => {( dispatch({type: CREATE_READING_SUCCESS, res: json}).then(dispatch(Notifications.success(showSuccess(json.book.title)))))},
(json) => { dispatch({type: CREATE_READING_ERROR400, res: json}).then(dispatch(Notifications.error(showError(json.error)))) },
(res) => { dispatch({type: CREATE_READING_ERROR500, res: res}) },
(ex) => { dispatch({type: CREATE_READING_FAILURE, error: ex}) },
)
}
}
As you can see the problem is in .then, since I dont know how to trigger action correctly.
You can also see request that is my helper function that looks like so (here I append token, return different responses):
export function request(url, options, success, error400, error, failure) {
let headers = new Headers();
headers.append("Content-Type", "application/json")
headers.append("Accept", "application/json")
options["headers"] = headers;
if (localStorage.jwtToken) {
let token = localStorage.jwtToken;
headers.append('Authorization', 'JWT '+token);
}
return fetch(url, options)
.then(res => {
if (res.status >= 200 && res.status < 300) {
res.json().then(json => {
return success(json)
})
} else if (res.status === 400) {
res.json().then(json => {
return error400(json)
})
} else {
return error(res)
}
}).catch((ex) => {
return failure(ex)
})
}
Question is how can I execute proper .then and what would be the correct way?

If you want to dispatch actions in chains you can actually implement it on your own.
Now say after analysing a bit you take a pen and paper and start write basic algorithm for how it should work and you come up with following:-
dispatch(action) => action returns function => action returns function => action returns an object(here you chain ends)
Now from above if you see that you create a middleware and keep on dispatching actions which return functions until you get an action with returns an object. This is what redux-thunk does.
So even if you try to create something of your own do that for your learning, but eventually you will come up with something like thunk or maybe some other package.
I would really say give redux-thunk a try.Also for middleware understanding I would recommend you can check redux middleware docs.

Related

How can I write a .then function with async/await, so that I can catch the response from axios (in seperate files and methods, in vue)

My target is to catch a respone, from a axios request, which works greate with .then, but I would like use async/await, since it is a new approach with lots of benefits.
(The update method is called multiple times)
How transform my saveEdit method (which gets a response form the update method) with async/await, so that I can catch the response from axios.
Method of my .vue file:
...
saveEdit (event, targetProperty, updateValue) {
this.update(this[updateValue])
.then((result) => {
if (result.status === 200) {
this.fetchData()
this.cancelEdit()
}
})
}
...
My function of my store module:
(api is a handler for axios, basicall axios. ...)
update ({ commit, rootGetters }, details) {
...
const requestUrl = `some adress`
return api
.patch(
requestUrl,
validatedDetails
)
.then(response => {
return response
})
.catch(error => {
return Promise.reject(error)
})
}
Other stackoverflow posts related to that problem, did answer my question, since in the examples are in one file and one method.
Thanks in advance
you can try someting like:
update ({ commit, rootGetters }, details) {
...
const requestUrl = `some adress`
return api.patch(
requestUrl,
validatedDetails
)
}
and :
async saveEdit (event, targetProperty, updateValue) {
try {
const result = await this.update(this[updateValue])
if (result.status === 200) {
this.fetchData()
this.cancelEdit()
}
} catch (error) {
// handle api call error
}
}

Async Redux Action: add auth token to fetch() request

I currently have an async redux action (w/ Middleware) which fetches data from my backend. I am adding Firebase authentication, and want to include the auth token to this request. The auth token is fetched asynchronously using the function getAuthToken() below. The below code doesn't work, complaining
Cannot read property 'resolve' of undefined
This simple task, chaining the output of one async to another, but I'm new to JS. How can I accomplish this?
// I'm sure returning from an async function like this is wrong.
function getAuthToken() {
firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(idToken => {
return idToken;
});
}
// Async redux action.
export function getData(userId) {
return dispatch => {
return fetch(`${BASE_URL}/data/${userId}`, {
headers: { Authorization: getAuthToken().resolve() } // Can I even resolve like this?
})
.then(response => response.json())
.then(json => dispatch(returnSurveys(json)));
};
}
Short-term solution: first await for token then send your request.
function getAuthToken() {
return firebase
.auth()
.currentUser.getIdToken(/* forceRefresh */ true)
.then(idToken => {
return idToken;
});
}
export function getData(userId) {
return dispatch => {
return getAuthToken().then(token =>
fetch(`${BASE_URL}/data/${userId}`, {
headers: { Authorization: token } // Can I even resolve like this?
})
.then(response => response.json())
.then(json => dispatch(returnSurveys(json))));
};
}
Downside: calling getAuthToken each time you want to ask backend for something is definitely bad practice.
So you better put token into Redux as well as other data is.
And then you may either pass token from outside:
export function getData(userId, token) { ....
Or, probably better, ask store for token right in your thunk(second parameter getState is passed by redux-thunk):
export function getData(userId) {
return (dispatch, getState) => {
return fetch(`${BASE_URL}/data/${userId}`, {
headers: { Authorization: getState().path.to.token.in.your.store }
})
For some period it assumed to be rather bad approach, that's why it's not widely known. But there are different points of view and to me your case suiting that very well.

How to get normal json object from Promise Object in react-redux [duplicate]

This question already has answers here:
Handling async request with React, Redux and Axios?
(4 answers)
Closed 5 years ago.
How to get normal JSON object from Promise Object in react-redux?
My action.js contains:
export function allEmp() {
let url = "employees?access_token=";
let result = ApiCall.getApiCall(url)
.then(function (response) {
return response;
})
.catch(function (error) {
return error;
});
console.log("result",result);
return {
type: "ALL_EMP",
payload: new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result);
}, 2000);
})
};
}
my API call configuration is:
getApiCall(url) {
let base_url = "http://192.168.1.151:3000/api/";
let api_token = "1f7169e92c1d0722db575b877707cf0b88b8f0ad";
let fetch_url = base_url + url + api_token;
let myHeaders = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json'
});
return fetch(fetch_url, {
method: "GET",
headers: myHeaders
})
.then(function(response) {
if (response.ok) {
return response.json();
} else {
var error = new Error(response.statusText);
error.response = response;
throw error;
}
})
.then(function(json) {
return json;
})
}
My store.js :
import {createStore, combineReducers, applyMiddleware} from "redux";
import thunk from "redux-thunk";
import promise from "redux-promise-middleware";
import userDetails from "./reducers/User_reducer";
export default createStore(
combineReducers({
userDetails
}),
{},
applyMiddleware(thunk, promise())
);
But here I'm getting Promise object as a result. So that when I'm assigning in payload it is getting as undefined. How should I manipulate it when I'm sending the response as payload.
Can anyone give me some suggestion?
react-redux does not support asynchronous action creators out-of-the-box, so you'll need to add a dependency to your project.
Take a look at the redux-thunk middleware, which adds support for asynchronous action creators.
The idea with redux-thunk is that your action creator will trigger another action once the asynchronous code resolves.
In your case, you would have an allEmp action creator that calls another action receiveAllEmp once the Promise resolves:
allEmp Action Creator (using redux-thunk)
export function allEmp() {
return (dispatch) => {
return ApiCall.getApiCall(url).then((response) => {
// Dispatch the receiveAllEmp action
dispatch(receiveAllEmp(response));
return response;
});
};
}
receiveAllEmp Action Creator (normal action creator)
export function receiveAllEmp(response) {
return {
type: "ALL_EMP",
payload: response,
};
}
Promises represent asynchronous processes whose may not be available immediately. They provide a way to trigger a handler when the result is ready (so your Javascript can go on and get other work done).
To access their result, you place a handler in a .then method which is called at resolution and receives the result as an argument.
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
You might consider writing your function:
export function allEmp() {
p = new Promise();
let url = "employees?access_token=";
ApiCall.getApiCall(url)
.then(function (response) {
console.log("result",result);
p.resolve({
type: "ALL_EMP",
payload: response})
})
.catch(function (error) {
console.log("error",error);
p.reject(error)
});
return p // allEmp now returns a promise which gives it a .then() method to contain your handler code.
}
Then call your function like this:
allEmp().then(
function(res){/*handle it here, not called until async resolves, receives result*/},
function(err){/*or resolves to error, receives error*/}
)
You might consider also having a look at the await/async syntax which makes it appear as if you code is waiting for async operations and can enhance readability in the majority of cases.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Reactjs and redux - How to prevent excessive api calls from a live-search component?

I have created this live-search component:
class SearchEngine extends Component {
constructor (props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
}
handleChange (e) {
this.props.handleInput(e.target.value); //Redux
}
handleSearch (input, token) {
this.props.handleSearch(input, token) //Redux
};
componentWillUpdate(nextProps) {
if(this.props.input !== nextProps.input){
this.handleSearch(nextProps.input, this.props.loginToken);
}
}
render () {
let data= this.props.result;
let searchResults = data.map(item=> {
return (
<div key={item.id}>
<h3>{item.title}</h3>
<hr />
<h4>by {item.artist}</h4>
<img alt={item.id} src={item.front_picture} />
</div>
)
});
}
return (
<div>
<input name='input'
type='text'
placeholder="Search..."
value={this.props.input}
onChange={this.handleChange} />
<button onClick={() => this.handleSearch(this.props.input, this.props.loginToken)}>
Go
</button>
<div className='search_results'>
{searchResults}
</div>
</div>
)
}
It is part of a React & Redux app I'm working on and is connected to the Redux store.
The thing is that when a user types in a search query, it fires an API call for each of the characters in the input, and creating an excessive API calling, resulting in bugs like showing results of previous queries, not following up with the current search input.
My api call (this.props.handleSearch):
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
fetch(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No items found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}
};
My intention is that it would be a live-search, and update itself based on user input. but I am trying to find a way to wait for the user to finish his input and then apply the changes to prevent the excessive API calling and bugs.
Suggestions?
EDIT:
Here's what worked for me.
Thanks to Hammerbot's amazing answer I managed to create my own class of QueueHandler.
export default class QueueHandler {
constructor () { // not passing any "queryFunction" parameter
this.requesting = false;
this.stack = [];
}
//instead of an "options" object I pass the api and the token for the "add" function.
//Using the options object caused errors.
add (api, token) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
api,
token,
resolve,
reject
});
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
api,
token,
resolve,
reject
};
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true;
// here I call fetch as a default with my api and token
fetch(this.stack[0].api, {
headers: {
'Content-Type': 'application/json',
'Authorization': this.stack[0].token
}
}).then(response => {
this.stack[0].resolve(response);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error);
this.requesting = false;
this.stack.splice(0, 1);
this.makeQuery()
})
}
}
I made a few changes in order for this to work for me (see comments).
I imported it and assigned a variable:
//searchActions.js file which contains my search related Redux actions
import QueueHandler from '../utils/QueueHandler';
let queue = new QueueHandler();
Then in my original handleSearch function:
export const handleSearch = (input, loginToken) => {
const API= `https://.../api/search/vector?query=${input}`;
}
return dispatch => {
queue.add(API, loginToken).then... //queue.add instead of fetch.
Hope this helps anyone!
I think that they are several strategies to handle the problem. I'm going to talk about 3 ways here.
The two first ways are "throttling" and "debouncing" your input. There is a very good article here that explains the different techniques: https://css-tricks.com/debouncing-throttling-explained-examples/
Debounce waits a given time to actually execute the function you want to execute. And if in this given time you make the same call, it will wait again this given time to see if you call it again. If you don't, it will execute the function. This is explicated with this image (taken from the article mentioned above):
Throttle executes the function directly, waits a given time for a new call and executes the last call made in this given time. The following schema explains it (taken from this article http://artemdemo.me/blog/throttling-vs-debouncing/):
I was using those first techniques at first but I found some downside to it. The main one was that I could not really control the rendering of my component.
Let's imagine the following function:
function makeApiCall () {
api.request({
url: '/api/foo',
method: 'get'
}).then(response => {
// Assign response data to some vars here
})
}
As you can see, the request uses an asynchronous process that will assign response data later. Now let's imagine two requests, and we always want to use the result of the last request that have been done. (That's what you want in a search input). But the result of the second request comes first before the result of the first request. That will result in your data containing the wrong response:
1. 0ms -> makeApiCall() -> 100ms -> assigns response to data
2. 10ms -> makeApiCall() -> 50ms -> assigns response to data
The solution for that to me was to create some sort of "queue". The behaviour of this queue is:
1 - If we add a task to the queue, the task goes in front of the queue.
2 - If we add a second task to the queue, the task goes in the second position.
3 - If we add a third task to the queue, the task replaces the second.
So there is a maximum of two tasks in the queue. As soon as the first task has ended, the second task is executed etc...
So you always have the same result, and you limit your api calls in function of many parameters. If the user has a slow internet connexion, the first request will take some time to execute, so there won't be a lot of requests.
Here is the code I used for this queue:
export default class HttpQueue {
constructor (queryFunction) {
this.requesting = false
this.stack = []
this.queryFunction = queryFunction
}
add (options) {
if (this.stack.length < 2) {
return new Promise ((resolve, reject) => {
this.stack.push({
options,
resolve,
reject
})
this.makeQuery()
})
}
return new Promise ((resolve, reject) => {
this.stack[1] = {
options,
resolve,
reject
}
this.makeQuery()
})
}
makeQuery () {
if (! this.stack.length || this.requesting) {
return null
}
this.requesting = true
this.queryFunction(this.stack[0].options).then(response => {
this.stack[0].resolve(response)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
}).catch(error => {
this.stack[0].reject(error)
this.requesting = false
this.stack.splice(0, 1)
this.makeQuery()
})
}
}
You can use it like this:
// First, you create a new HttpQueue and tell it what to use to make your api calls. In your case, that would be your "fetch()" function:
let queue = new HttpQueue(fetch)
// Then, you can add calls to the queue, and handle the response as you would have done it before:
queue.add(API, {
headers: {
'Content-Type': 'application/json',
'Authorization': loginToken
}
}).then(res => {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(res => res.json()).then(data => {
if(data.length === 0){
dispatch(handleResult('No vinyls found.'));
}else{
dispatch(handleResult(data));
}
}).catch((error) => {
console.log(error);
});
}

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