I want to load a bunch of data that involves multiple APIs, I did setState in foreach, it worked but I think the design is wrong, as I see flickering on my screen.
API.fetchMain().then(main => {
main.forEach(o => {
const main_id = o.main_id
this.setState({
main: o
})
API.fetchSub(main_id)
.then(sub => {
this.setState({
sub
})
API.fetchOthers(main_id, sub.id)
.then(others => {
this.setState({
others
})
})
})
})
}
I think I should use promises to refactor, I tried but I think my design was wrong.
API.fetchMain().then(main => {
let promise = []
main.forEach(o => {
const main_id = o.main_id
this.setState({
main: o
})
promise.push(
API.fetchSub(main_id)
.then(sub => {
return API.fetchOthers(main_id, sub.id)
})
)
})
Promise.all(promise).then(resp => console.log('do setState here'))
}
Need help.
It looks to me that you are fetching a resource that provides you with information about how to make further requests. If you are open to using a fetch library I would recommend axios. Heres how I would envision it looking
import axios from 'axios'
fetch(){
// Make the initial request
var options = { url: "URL of main resource", method: "GET" }
axios(options).then(res => {
// Create an array of next requests from the response
var next_requests = res.data.main.map(id => axios.get(`${resource_url}/${id}`))
//Make the requests in parallel
axios.all(next_requests).then(axios.spread(() => {
//since we don't know how many request we can iterate
//over the arguments object and build the new state
var newState = {}
arguments.forEach(i => {
// how you want to structure the the state before setting it
})
this.setState(newState)
})
}).catch(err => //error handling logic here)
}
From my understanding of your question you could also (since you are using react) break your fetch request into components that get called when they mount. A quick example:
const class MainComp extends Component {
constructor(props){
super(props)
this.state = {
main: []
}
}
componentDidMount(){ this.fetchMain() }
fetchMain() {
axios.get('url').then(res =>
this.setState({main_id: res.data.main.id})
}
sendSubFetchToParent(dataFromChild){
// Do what you need to with the data from the SubComp child
}
render(){
return (
{this.state.main.map(id => <SubComp id={id) afterFetch={this.sendSubFetchToParent}/>}
)
}
}
const class SubComp extends Component {
componentDidMount(){ this.fetchSub() }
fetchSub() {
//Pass the results to the parent after the fetch completes.
// You can add the usual error handling here as well.
axios.get('url').then(res => this.props.afterFetch(res.data))
}
render(){
//Return null or render another sub component for further nested requests
return null
}
}
In the above, MainComp initiates a request. When it gets a response (which in your example is an array) we set that response to the state. This triggers rerender which will mount n number of SubComp. When those mount they will initiate their requests to get data. For SubComp we pass a callback from the parent so SubComp can send its fetch response back to MainComp (And handle it appropriately by setting state etc etc). You can return null in SubComp or have it mount a component that will make further request.
In that way your fetch requests are now componentized.
Related
I am having trouble rendering my objects using .map() within React / NextJS.
I have a function where I get images from Firebase Cloud Storage, code below:
getImages = () => {
let firebase = loadFirebase()
var storageRef = firebase.storage().ref('chest')
let { state } = this
storageRef.listAll().then((result) => {
let data = []
result.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
data.push(url)
}).catch((error) => {
// Handle any errors
})
})
state.images = data
this.setState({ images: data })
}).catch((error) => {
// Handle any errors
})
}
This part seems to work as I do get data back and the state is updated, results as in screenshot:
Results after setState
I then map through images with the code below:
{ this.state.images.map((image, index) => {
return (
<img
key={ index }
src={ image }
alt=""
/>
)
})}
On the same page as this, I have other places where I get data from Firebase, set my states accordingly and render the objects using .map(). In those cases it works perfectly fine. Difference is that in those cases I use getInitialProps() to get my data from Firebase, whereas with the data from Cloud Storage I have a function, the getImages() function above, that is called on componentDidMount()
But in both cases the state is set in componentDidMount() and the final result returned of this.state looks like the screenshot attached.
Any help and / or improvements on this will be much appreciated.
You should never set the state values manually. You should just remove the line that sets the images in the state before calling setState. That line prevents the rendering since after that react can not detect any changes when you set the state using setState:
getImages = () => {
let firebase = loadFirebase()
var storageRef = firebase.storage().ref('chest')
storageRef.listAll().then((result) => {
let data = []
result.items.forEach((imageRef) => {
imageRef.getDownloadURL().then((url) => {
data.push(url);
this.setState({ images: data });
}).catch((error) => {
// Handle any errors
})
});
}).catch((error) => {
// Handle any errors
})
}
I have a big application in React-Native and I have a lot of duplicate functions in the code.
So I created a file called function.js which could contain all my duplicate functions. Like queries on the local database, remote data base...
So I pretty much got the job done. However I have a problem.
This two functions must be used one after the other.
The first one does an update of the state to get a user id from local database.
The second one, asks information from the remote database with the user id retrieved by the first function in parameters.
When both calls are in the componentdidmount element, unfortunately it doesn't work !!
The update time of the state by the first function is too slow compared to the execution of the second function.
The second function gets an "undefined" parameter when it is executed. because the state is not updated by the first function for the moment.
If I put the second function in componentDidUpdate() it works but it runs in a loop so it's not a solution either.
I also don't want to trigger the execution of the second function at the end of the first one in the external file. It would make the functions not autonomous from each other.
And I think that the solution of a timeout() is not very good either, even if we could work with it.
Example code :
It's the content of my App.js file that imports the Function.js file containing all my functions
import React, { Component } from 'react';
import { fetchUser, prepareUserData } from 'bitcoin/Functions/Function'
export default class Profile extends Component {
state = {
user_id: "",
}
componentDidMount() {
fetchUser.call(this);
prepareUserData.call(this, this.state.user_id)
}
render{
return (<View></View>)
}
This is the content of my Function.js file which contains functions that are duplicated in my application.
import * as SQLite from "expo-sqlite";
import axios from "axios";
const db = SQLite.openDatabase("db.db");
/* #############################################################################
User data retrieval function in the local database
##############################################################################*/
export function fetchUser () {
let query = "select * from ?";
let params = [];
db.transaction(tx => {
tx.executeSql(
query,
params,
(_, { rows: { _array } }) => {
this.setState({user_id: _array[0].user_id})
},
function(tx, err) {
console.log("Erreur" + err);
}
);
});
}
/* #############################################################################
User data retrieval function in the remote database
##############################################################################*/
export function prepareUserData(userID) {
let userConnect = new FormData();
userConnect.append("id", userID);
console.log(userConnect)
const url =
"https://*************/rest_api/React_native_api/appmobile_profile";
axios
.post(url, userConnect)
.then(res => {
console.log(res.data)
if(res.status === 200){
this.setState(
{
user_pseudo: res.data.pseudo,
[ ... ]
user_lastName: res.data.last_name,
},);
}
})
.catch(err => {
console.log("Erreuur", err);
});
}
I've tried a lot of things with async componentDidMount(), await myfunc(), creating asynchronous functions in my function file ...
But I can't find solutions. I could do otherwise but I find the problem really interesting.
I think there is a way to optimize my use of react native.
Thank you for your various feedbacks. Have a nice day.
Okay, so I've come up with a effective solution to this problem.
I don't know much about promise and yet it seems to be a key for a lot element in react-native and javascript in general.
I'll share my code. Hopefully it can be useful to someone in the future! thank you for your prompt return and see you soon!
To make it work I used:
The Promise Object new Promise(function(resolve,reject)
Creation of a dynamic SQL query by adding parameters when calling the function.
File App.js
import React, { Component } from 'react';
import { selectFromTable, prepareUserData } from 'bitcoin/Functions/Function'
export default class Profile extends Component {
state = {
user_data: "",
}
componentDidMount() {
selectFromTable('user', ['*']).then((result) => {
this.setState({ user_data: result.rows._array[0] },() =>
{prepareUserData.call(this, this.state.user_data.user_id)})
})
}
render{
return (<View></View>)
}
File Function.js
export function selectFromTable(table_name, selected_columns, conditions, callback) {
return new Promise(function(resolve,reject) {
let db_cmd = `SELECT ${selected_columns} FROM ${table_name}`;
db.transaction((tx, err) => {
tx.executeSql(db_cmd, [], (tx, res) => {
resolve(res);
},
function(tx, err) {
console.log("Error" + err);
}
);
});
});
}
export function prepareUserData(userID) {
let userConnect = new FormData();
userConnect.append("id", userID);
const url =
"https://**********/**********/appmobile_profile";
axios
.post(url, userConnect)
.then(res => {
console.log(res.data)
if(res.status === 200){
this.setState(
{
user_pseudo: res.data.pseudo,
user_cellphone: res.data.cellphone,
user_callingCode: res.data.calling_code,
});
};
})
.catch(err => {
console.log("Error", err);
});
}
I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()
How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}
I've just started a simple weather-app project to train React and data fetching.
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.state = {
city: "",
id: 0
}
this.choseCity = this.choseCity.bind(this);
this.cityName = this.cityName.bind(this);
this.cityInfo = this.cityInfo.bind(this);
}
chooseCity(e) {
console.log(e.target.value)
this.setState({
city: e.target.value
});
}
cityName() {
axios.get(`https://www.metaweather.com/api/location/search/?query=${this.state.city}`)
.then(res => res.data)
.then(data => this.setState({
city: data[0].title,
id: data[0].woeid}))
}
cityInfo() {
axios.get(`https://www.metaweather.com/api/location/${this.state.id}/`)
.then(res => console.log(res.data))
}
render() {
return (
<div className="App">
<input type="text" placeholder="Enter city name" value={this.state.city} onChange={this.chooseCity}/>
<button onClick={this.cityName}>Name</button>
<button onClick={this.cityInfo}>Info</button>
</div>
);
}
}
export default App;
So, I have 2 functions (cityName and cityInfo) that I'm able to execute on 2 different onClick events. They both seem to work independently.
cityName() requests data that is stored in my state.
cityInfo() uses this state in the url of the request to get further informations.
I'm trying to chain them to be able to retrieve all the data in one call, but since they're both async, my second request starts before the data from the first one is stored in my state, and there is no way in the api I can directly get the info I need in one request.
I've tried a couple of things like grouping them in a single function, but nothing conclusive so far.
Solution: by #elsyr
this is how to chain in one function, using data between the requests:
cityInfo() {
axios.get(`https://www.metaweather.com/api/location/search/?query=${this.state.city}`)
.then(res => res.data)
.then(data => {
axios.get('https://www.metaweather.com/api/location/' + data[0].woeid)
.then(res => res.data)
.then (data => this.setState({
info: data
}))
});
}
From what I can tell, you want to trigger a call as in cityInfo right after the state is set in cityName.
This is entirely possible - setState is asynchronous (as it seems you've figured out), but setState also has an optional callback parameter that you can use. The callback is guaranteed to fire off after the state is modified.
Your code could look something like this in cityName:
cityName() {
axios.get(`https://www.metaweather.com/api/location/search/?query=${this.state.city}`)
.then(res => res.data)
.then(data => this.setState({
city: data[0].title,
id: data[0].woeid}),
this.cityInfo); // callback here! runs after the state is set!
}
The other option you have is just to chain together your axios calls. If we look your chunk of code here:
cityName() {
axios.get(`https://www.metaweather.com/api/location/search/?query=${this.state.city}`)
.then(res => res.data)
.then(data => {
// data here already contains the id - data[0].woeid
// we can just use it here to kick off another request - something like:
// axios.get('https://www.metaweather.com/api/location/' + data[0].woeid)
});
}
Because you used .then(), you know that you're guaranteed to have your data before you start off your cityInfo call. This will probably take a bit more refactoring, but it's probably a better idea if you're only keeping the state from cityName to use it in cityInfo.