whats the proper way to make dependent api call in react redux - reactjs

In my application, we maintain an inventory of authors and books and the vendors who are selling those books.
In my react js app I have a home page which displays a table with a list of authors and when we click on any author we go to author details page and URL will be like ${url}/author/12 . This author details page consists of three sections .First div contains author details like name, city, country.Second div contains list of books author has published so far which is a table and by default first record is selected.Third div contains the details of each book like title, description, price when a book is selected from second div .
In this author details page i use the id from props.match.params.id and make first api call to fetch author details, if success then fetch books if that is success get selected book details. I am confused between two approaches i have.
Method 1:
Make all the api calls from ui component based on data recieved. Make use of componentwillReceiveProps to know when the author details are fetched or books are fetched. Is componentwillReceiveProps the right place to decide to make subsequent api calls ?
class AuthorPage extends React.Component {
state = {
authorId:this.props.match.params.id,
}
componentDidMount() {
this.props(fetchAuthorDetails(authorId));
}
componentWillReceiveProps(newProps){
//Is this the right place to decide the subsequent api calls ?
if(isNotEmpty(newProps.author.details))
{
this.props(fetchAuthorBooks(authorId));
}
else if (newProps.author.books.length > 0 && isNotEmpty(newProps.author.selectedBook)){
this.props(fetchSelectedBookDetails(newProps.author.selectedBook.id));
}
}
render() {
// UI render code goess here
}
}
const mapDispatchToProps = dispatch => bindActionCreators(
{
fetchAuthorDetails,
fetchAuthorBooks,
fetchSelectedBookDetails
},
dispatch,
)
const mapStateToProps = (state, ownProps) => ({
authorDetails: state.author.details,
books: state.author.books,
selectedBook: state.author.selectedBook,
selectedBookDetails : state.author.selectedBook.details
});
export default connect(mapStateToProps)(AuthorPage);
Method2:
Is it better to make Subsequent dispatch calls (from ActionCreators dispatch calls depending on success/fail) like I am calling authorDetailsSuccess /BooksActionCreators.getAuthorBookDetails and authorDetailsError/ErrorsActionsCreator in the below code? And should i have separate action creators file for authors and books.
export function fetchAuthorDetails(authorId) {
dispatch(authorDetailsStart());
return dispatch =>
fetch('http://localhost/api', {
method: 'get',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: userData.email,
password: userData.password,
}),
})
.then(response => {
if (response.status >= 200 && response.status < 300) {
console.log(response);
dispatch(authorDetailsSuccess(authorDetails));
dispatch(BooksActionCrerators.getAuthorBookDetails(authorDetails.id)); //is this the right place to chain
} else {
const error = new Error(response.statusText);
error.response = response;
dispatch(authorDetailsError());
dispatch(ErrorsActionsCreator.sendError(error));
throw error;
}
})
.catch(error => { console.log('request failed', error); });
}

Personally I always add redux-thunk module to my projects. This module transform a simple redux action to a promise-like function. So I can concatenate multiple actions by using classic .then() sintax. Check documentation for more details.

Related

why Axios promises doesn't get executed in order in React

useEffect(() => {
if(reviewsData) {
reviewsData.forEach( item =>
{
if(item)
{
item.text.forEach((review,i) =>
{
axios({
method: "post",
url: "http://localhost:5000/retrieveuserbyid",
data: {
userId: item.user
},
headers: {
authToken: localStorage.getItem("authToken"),
"Content-Type": "application/json"
}
}).then(res => {
const name =res.data.name;
setTempUser([...tempUser , name]);
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
}).catch(err => {
});
}
)
}
}
);
}
}, [reviewsData]
);
reviewsData.text are comments people post on my site and I want to retriev username for each person leaving comments. (store them into a array initially) but I don't understand why in the state tempUser I have only the last user and in the tempUser2 I have all the users but in a random order each time I refresh. *One person can leave multiple comments.
React state updates are asynchronous. When you queue up all the axios promises the current value of tempUser is enclosed and used for all the state updates. If you use a functional state update, however, they can be correctly queued up and processed. Functional state updates allow each queued update to access the previous update, i.e. each updated depends on the previous one.
then(res => {
const name =res.data.name;
setTempUser(tempUser => ([...tempUser , name]));
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
})
Side note: Axios is capable of concurrent fetches (axios.all), and since axios uses promises, you can also map a bunch of requests and use Promise.all, which returns an array of all resolved axios requests, in the order they were queued up (if order matters).

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

rerender after fetch POST and PUT request

I have an app that GET's data from a REST API. I am able to fetch the data. I also need to be able to POST data to the API and PUT edits to the API. My code is able to successfully perform all of these operations, however I need to refresh the page to see the submissions. The trouble I am having is with automatically rerender the page upon submissions. I know I either have to setState or refetch the API after the call. I do not know how to structure this though. Any suggestions?
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
isLoaded: false,
}
this.editBeersLikes = this.editBeersLikes.bind(this);
this.addNewBeer = this.addNewBeer.bind(this);
}
seeBeers() {
return fetch("https://beer.fluentcloud.com/v1/beer/")//specify id number to show single beer ex. "https://beer.fluentcloud.com/v1/beer/99"
.then(response => response.json())
.then(responseJson => {
this.setState({
isLoaded: true,
dataSource: responseJson,
});
return responseJson;
})
.catch(error => console.log(error)); //to catch the errors if any
}
addNewBeer() {
fetch("https://beer.fluentcloud.com/v1/beer/", {
body: "{\"name\":\"\",\"likes\":\"\"}",//input beer name and like amount ex "{\"name\":\"Michelob Ultra\",\"likes\":\"-5\"}"
headers: {
"Content-Type": "application/json"
},
method: "POST"
})
console.log("Beer Added!");
}
editBeersLikes() {
fetch("https://beer.fluentcloud.com/v1/beer/99", {//must specify id number to edit a single beer's likes ex "https://beer.fluentcloud.com/v1/beer/99"
body: "{\"likes\":\"\"}", //input amount of likes to edit ex "{\"likes\":\"22\"}"
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
method: "PUT"
})
console.log("Likes Successfully Updated!");
}
componentDidMount() {
this.seeBeers();
//this.editBeersLikes(); //uncomment when you want to edit likes
//this.addNewBeer(); //uncomment when you want to add beers
}
render() {
return (
<div className="App">
<h1>What is in My Fridge?</h1>
<ul>
{this.state.dataSource.map(dataSource => {
return <li key={`dataSource-${dataSource.id}`}>{dataSource.name} | {dataSource.likes}</li>
})}
</ul>
</div>
);
}
}
export default App;
The easiest way is probably to re-fetch the data after you have successfully POSTed or PUT them to the server. That would also be the cleanest and least error prone way because you always get a fresh response from the server with the most recent set of data.
However, you could use a pattern that is often referred to as "optimistic ui", meaning that you keep two states: the current state and the state how it will look like if the server request is successful. You would then push the new or updated item to your dataSource state and show it in your UI but revert it to the old state from before the server request if the request fails.
The downside of the latter approach is that you sometimes don't know all the data (especially auto generated IDs or lastUpdated fields) without querying the server again.
So if you want a relatively easy, clear, and solid solution you can call this.seeBeers() after each POST/PUT request to receive an updated set of data.
Keep in mind that this might not always work as expected when you're using e.g. sharding or an elastic search layer for read operations as they might not be in sync directly after you finished the write operation. That's probably not relevant for you though, as that's usually only a challenge when working with large scale setups.

React Redux wait for state change

I Want to extract all my server call functions (HTTP GET, POST, ...) into one helper function. The helper function will be called from different react components. It will check if the JWT Token is expired or not (client side) and if expired will ask user to relogin and get a new token before sending the request to the server.
Problem: When the helper function finds out the Token is expired it will dispatch an action to show the Login Dialog however it will continue the code calling the fetch. I need to wait for the Login Dialog to change the LoggedIn state and token before calling the server however it doesn't seem to be possible to watch for this state change. (One idea is returning a promise from Login dialog however I can't understand how to return a promise and where from!)
I appreciate that all the above can be very abstract and difficult to follow so have created a full code example showing what I need.
Sample Code
*PS : If the code sandbox fails please refresh it. They seem to be having some race issue with some of the plugins!
Is this what you are looking for?
componentDidUpdate(prevProps) {
if (!prevProps.loggedIn && this.props.loggedIn) {
// User just logged in
}
}
I am not a specialist of thunk yet, but what i can say is that you serverCall function must return a function with a dispatch parameter (see examples here)
You must dispatch an action in this sub function (in fact, call an action creator which will put the data in the application state.
You don't have to make an explicit promise because fetch return already a promise.
I will try something like :
export const serverCall = (
url,
method,
body = undefined,
successMessage = undefined
) => {
// your code
return function (dispatch) {
return fetch(url, {
method: method,
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
...(body && { body: JSON.stringify(body) })
}).then(response => response.JSON())
.then(response =>
if (response.ok) {
if (successMessage) {
console.log(successMessage);
}
dispatch(fetchData(response))
} else {
index.js
<Button
onClick={() =>
this.props.serverCall(
"https://jsonplaceholder.typicode.com/users",
"GET"
)
>
The state is to be removed here if you use Redux. All is taken from props via mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
onLogin: (username, password) => dispatch(login(username, password)),
ToggleIsLoginDialogOpen: IsLoginDialogOpen =>
dispatch(toggleIsLoginDialogOpen(IsLoginDialogOpen)),
serverCall: (url, method) => dispatch(serverCall(url, method))
});

Data fetching with React Native + Redux not working

I am building my first React Native app and use Redux for the data flow inside my app.
I want to load some data from my Parse backend and display it on a ListView. My only issues at the moment is that for some reason, the request that I create using fetch() for some reason isn't actually fired. I went through the documentation and examples in the Redux docs and also read this really nice blog post. They essentially do what I am trying to achieve, but I don't know where my implementation differs from their code samples.
Here is what I have setup at the moment (shortened to show only relevant parts):
OverviewRootComponent.js
class OverviewRootComponent extends Component {
componentDidMount() {
const { dispatch } = this.props
dispatch( fetchOrganizations() )
}
}
Actions.js
export const fetchOrganizations = () => {
console.log('Actions - fetchOrganizations');
return (dispatch) => {
console.log('Actions - return promise');
return
fetch('https://api.parse.com/1/classes/Organization', {
method: 'GET',
headers: {
'X-Parse-Application-Id': 'xxx',
'X-Parse-REST-API-Key': 'xxx',
}
})
.then( (response) => {
console.log('fetchOrganizations - did receive response: ', response)
response.text()
})
.then( (responseText) => {
console.log('fetchOrganizations - received response, now dispatch: ', responseText);
dispatch( receiveOrganizations(responseText) )
})
.catch( (error) => {
console.warn(error)
})
}
}
When I am calling dispatch( fetchOrganizations() ) like this, I do see the logs until Actions - return promise, but it doesn't seem to actually to fire off the request. I'm not really sure how how I can further debug this or what resources to consult that help me solve this issue.
I'm assuming that Redux is expecting a Promise rather than a function.. Is that true?
If so, I think your return function may not be working.
You have a new line after your return, and it's possible JavaScript is (helpfully) inserting a semicolon there.
See here: Why doesn't a Javascript return statement work when the return value is on a new line?

Resources