I am attempting to create a set of joined data from two apis that I would love to implement in a table, apparently I am not getting the expected result.
The logic behind my code is :
Get the data from the first api
loop through each element in the data to get a specific data from the second api depending with the id of each element.
Create a new key to each element, each data obtained in second api as a value.
the resulting data is set in the state.
I have been able to accomplish step 1 to 3 except step 4.
class App extends React.Component{
constructor(props){
super(props);
this.state = {
Data: [],
};
}
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
. then(res => res.json())
.then(data => data['results'].forEach(element => {
fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => res.json())
.then(data2 => element['rundata'] = data2)
.then(this.state.Data.push(element))
}))
.catch(err => console.log(err))
}
render(){
console.log('data', this.state.Data)
return(
)
}
}
export default App;
You can only update react state with setState method.
Try the below code.
then(this.setState(prev => ({
data: [...prev, element]
})
Well the main reason for the issue is that you are not using setState correctly.
you need to use setState, and you need to not mutate the state. Always copy the old state and then modify it.
The second issue which may occur is that your forEach method is not synchronised.
Try changing your componentDidMount to this:
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
.then(res => res.json())
.then(data => {
for (const element of data['results']) {
fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => res.json())
.then(data2 => element['rundata'] = data2)
.then(this.setState({ Data: [...Data, element] })
}
}
.catch(err => console.log(err))
}
this.setState(prevState => ({
myArray: [...prevState.myArray, "new value"]
}))
in the other hand, i don't recommend to loop in fetch, either you use a post method and loop throw all the ids in your server side and return an array, or if you have a light database you get all the result and do a loop in your client side.
When you're setting data into the state in React you only need to use this.setState().
So for your case you only have to run this.setState({ Data: element }).
Maybe this :
componentDidMount() {
fetch('http://localhost:8000/tasks?format=json')
.then(res => res.json())
// create array of promise
.then(data => data.results.map(element => {
return fetch(`http://localhost:8000/task/${element.id}/runs`)
.then(res => element.rundata = res.json())
.cath(err => console.log(err))
}))
.then((arrayPromise) => {
Promise.all(arrayPromise)
.then(resolved => this.setState({data:resolved}))
})
.catch(err => console.log(err))
}
render(){
console.log('data', this.state.data)
return(
<></>
)
}
}
Related
So I'm creating a simple MERN App, backend is working properly, but when working with useState hook in frontend is causing issues.
what im trying to do is to fetch "users" data(an array of object with field username) from backend endpoints, and updating the users array which is a hook, but it only updates with the last itm of the incoming username and not list of all usernames!!
code for fetching and updating the hook:
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
data.map((itm) => {
console.log([itm.username]) //line 19
setUsers([...users, itm.username])
})
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users); //line 30
what I want is to get a list of usernames in the "users" state!
something like this:
users = ["spidey", "thor", "ironman", "captain america"]
console.log is also not showing any errors...
console window
pls help, can't figure out where it's getting wrong?
The issue is two-fold, first you are using Array.prototype.map to iterate an array but are issuing unintentional side-effects (the state updates), and second, you are enqueueing state updates in a loop but using standard updates, each subsequent update overwrites the previous so only the last enqueued update is what you see in the next render.
Use either a .forEach to loop over the data and use a functional state update to correctly update from the previous state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
data.forEach((itm) => {
console.log([itm.username]);
setUsers(users => [...users, itm.username]);
})
})
.catch(err => console.log(err));
}
Or use the .map and just map data to the array you want to append to the users state.
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data);
setUsers(users => users.concat(data.map(itm => itm.username)));
})
.catch(err => console.log(err));
}
you can set the map result in a variable after that you can call the useState on it.
const [users, setUsers] = useState([]);
const getUsers = () => {
fetch("http://localhost:5000/users")
.then(res => res.json())
.then(data => {
console.log(data); //line 17
const userNameData = data.map(itm => itm.username)
setUsers(...users, userNameData)
})
.catch(err => console.log(err))
}
useEffect(() => {
getUsers();
}, [])
console.log(users);
I was tasked to create a front-end for a poc. I am not a front end developer but I chose to use React JS.
There is only one page fetching data from multiple API endpoints. API endpoints return a simple json object.
I managed to get that to work however my code is ugly af and I want to create a function to handle all of that but I can't seem to get it right. Here's my code
export default class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
group1: [],
group2: [],
group3: [],
isLoaded: false,
}
}
componentDidMount() {
const group1_url = "http://localhost/api/1"
const group2_url = "http://localhost/api/2"
const group3_url = "http://localhost/api/3"
fetch(group1_url)
.then(res => res.json())
.then(json => {
this.setState({
group1: json,
})
});
fetch(group2_url)
.then(res => res.json())
.then(json => {
this.setState({
group2: json,
})
});
fetch(group3_url)
.then(res => res.json())
.then(json => {
this.setState({
group3: json,
})
});
}
I am trying to create a function like this:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
state: json,
})
});
}
var group1 = fetch_data(group1_url, group1);
So far no joy. How can I create a function to fetch data and set a state in js?
Alternatively how can I make my code look better? Or is there something else I should use/look into?
Pass a string as the second parameter, and use a computed property:
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
fetch_data(group1_url, 'group1');
I'd also highly recommend catching errors - possible unhandled rejections should be avoided whenever possible.
You might want to use Promise.all to wait for all groups to load:
const dataSources = {
group1: 'http://localhost/api/1',
group2: 'http://localhost/api/2',
group3: 'http://localhost/api/3',
};
Promise.all(
Object.entries(dataSources).map(([propertyName, url]) => (
fetch(url)
.then(res => res.json())
.then((result) => {
this.setState({
[propertyName]: result
})
})
))
)
.then(() => {
this.setState({ isLoaded: true })
})
.catch((error) => {
// handle errors
})
(also note that your json argument is not a JSON - JSON is only a format that exists with strings. Something that has been deserialized is just a plain object or array. Better to call it something less misleading, like result as I did)
You could try Promise.all
Promise.all takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.
const points = [
"http://localhost/api/1",
"http://localhost/api/2",
"http://localhost/api/3",
];
const responses = await Promise.all(points.map((point) => fetch(point)));
const data = await Promise.all(responses.map((response) => response.json()));
const [group1, group2, group3] = data;
this.setState({
group1,
group2,
group3,
});
Just remember to wrap this logic in an async function
You can do something like this.
function fetch_data(url, state) {
fetch(url)
.then(res => res.json())
.then(json => {
this.setState({
[state]: json,
})
});
}
var group1 = fetch_data(group1_url, 'group1');
I'd like to define a function that makes a fetch request based on its parameter so I can simplify the fetch calls inside componentDidMount(). I've tried the code below but it gives an Unexpected token error for the setState function. Is there a way to use the parameter of the function inside then()?
constructor() {
super();
// cases in date order from 1 to 5. Cases5 is the latest.
this.state = {
cases1: [],
cases2: [],
cases3: [],
cases4: [],
cases5: [],
};
}
componentDidMount() {
fetch("/cases/1")
.then((res) => res.json())
.then((cases1) => this.setState({ cases1 }));
fetch("/cases/2")
.then((res) => res.json())
.then((cases2) => this.setState({ cases2 }));
fetch("/cases/3")
.then((res) => res.json())
.then((cases3) => this.setState({ cases3 }));
fetch("/cases/4")
.then((res) => res.json())
.then((cases4) => this.setState({ cases4 }));
fetch("/cases/5")
.then((res) => res.json())
.then((cases5) => this.setState({ cases5 }));
}
fetchCaseData = (index) => {
fetch(`/cases${index}`)
.then((res) => res.json())
.then((`cases${index}`) => this.setState({ `cases${index}` }));
}
You can't simply declare a dynamic name for a variable. You can however set dynamic keys for an object using brackets notation but not object shorthand syntax
Updated code will look like below
.then((val) => this.setState({ [`cases${index}`]: val }));
as you can see on , i have two way to get the data.
The first one is localy, use the getdata() to read from local file.
function getData() {
const data = testData.map(item => {
return {
...item
};
});
return data;
}
The second one is a distance.
componentDidMount() {
fetch('https://XXXXX')
.then(res => res.json())
.then((data) => {
this.setState({ todos: data })
console.log(this.state.todos)
})
}
both work, but somehow i can not bind the second one to my app.
i am newbie on react technologie.
I think you can't define 2 state obj.
you have to delete one of them
I have a problem related to set State in reactjs.
I need to fetch 2 APIs, then would like to compare an object (called team_lead) with an object (userProfile). Whether an id of the object (team_lead) is equal with in an id of the other object (userProfile) or not. If 2 ids are equal, I will set the state: auth: true. My 'apply' button will be only displayed whether auth: true. My idea is the button will be only shown if the user is logged-in and the user is also a team leader.
export class TeamInfo extends React.Component {
state = {
checked_auth: false,
team_lead:[],
userobject: {}
}
componentDidMount(){
axios
.get(`/teams/13`)
.then(responseData => {
this.setState({
teamlead: responseData.data.team_lead,
})
})
.catch(error => console.error(error));
axios
.get(`/profiles/?user=${userObject.user.id}`)
.then(responseData => {
if (this.state.teamlead.id === responseData.data[0].id) {
console.log("tea ok")
this.setState({
checked_auth: true
})
}
})
.catch(error => console.error(error));
}
render(){
return (
{this.state.checked_auth ? <Button> Apply </Button> : <div></div>}
)
}
}
I signed in and checked. Sometimes, the "Apply" button is shown. However, most of the time, the button is hidden. Could someone help me for this case?
The issue is that you are sending 2 asynchronous request. But the second depend on the first one.
you can use Promise.all to solve this problem.
componentDidMount(){
const firstRequest = axios
.get(`/teams/13`)
.then(responseData => responseData.data.team_lead)
.catch(error => console.error(error));
const secondRequest = axios
.get(`/profiles/?user=${userObject.user.id}`)
.then(responseData => responseData.data[0])
.catch(error => console.error(error));
Promise.all([firstRequest, secondRequest]).then(values =>
if (values[0].id === values[1].id) {
console.log("tea ok")
this.setState({
checked_auth: true
})
}
);
}