The question is how to modify data received from fetch Request in React?
1)I get the result of a fetch request and put in into state
2)To work with the data I need to modify it (add an ID and a boolean to each item of the array inside one of the objects of the coming array.
I tried the following way, but it doesn't work:
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=10&difficulty=easy&type=multiple"
)
.then((res) => res.json())
.then((data) => data.map(question => ({
...question,
incorrect_answers: question.incorrect_answers.map(answer => {...answer, isSelected: false})
})));
}, []); ```
Your data is a object so you can't map over it. The data object has a property results which contains the array of questions.
data.results.map((question) => ({ ... })
Then the first time you correctly surround the object you return with () but when you map over the incorrect_answers you don't.
incorrect_answers: question.incorrect_answers.map((answer) => ({
...answer,
isSelected: false,
}));
Full code
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=10&difficulty=easy&type=multiple"
)
.then((res) => res.json())
.then((data) =>
data.results.map((question) => ({
...question,
incorrect_answers: question.incorrect_answers.map((answer) => ({
answer,
isSelected: false,
})),
}));
);
}, []);
Related
I'm trying to write a custom hook useFetch that makes many requests for different data.
The problem is that I can't typing my date correctly, I have state like this.
interface IState<T> {
data: T | T[]
loading: boolean,
error: boolean,
}
Initial state
const initialState = {
data: [],
loading: false,
error: false,
}
useEffect
useEffect(() => {
setData((prevState) => ({...prevState, loading: true}));
axios.get(url)
.then(response => response.data.results ? response.data.results as T[] : response.data as T)
.then((data) => (setData((prevState) => ({...prevState, loading: false, data}))))
.catch(e => {
setData((prevState) => ({...prevState, error: true}));
if (e instanceof Error) console.log(e.message);
})
}, []);
When i get response.data.results i have array of objects and when i have response.data i have only one object. I'm trying to return the right type.
I call my function like this useFetch
and I have result like this MovieType | MovieType[].
I don't need TypeGuard I need only one correct type.
If you see any problems please let me know.
Thanks a lot!
It's impossible for this example to have a singular type when unionized.
The generic function has no idea which one it will output as T[] or a T object because of how you defined IState.
You would just have to check if it's an array or not when you read .data from the hook.
The only way to have a singular output type would be to remove the union T[] and simply cast both like Sana Mumtaz said.
interface IState<T> {
data: T
loading: boolean,
error: boolean,
}
const useFetch = <T>(): IState<T> => {
// ...
response => (response.data.results ? response.data.results : response.data) as T
}
useFetch<MovieType[]>();
Axios.get<T> is a generic function, so you can take advantage of this.
Try
useEffect(() => {
setData((prevState) => ({ ...prevState, loading: true }));
axios.get<{ results: T[] } | T>(url)
.then(({ data }) => "results" in data ? data.results : data)
.then(data => {
setData(prevState => ({ ...prevState, loading: false, data }))
})
.catch(e => {
setData((prevState) => ({ ...prevState, error: true }));
if (e instanceof Error) console.log(e.message);
})
}, []);
See playground example here
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 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(
<></>
)
}
}
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 }));
I have a problem with updating my state from nested axios call inside forEach loop:
constructor(props) {
super(props);
this.state = {
isLoaded: false,
items: []
};
//Binding fetch function to component's this
this.fetchFiles = this.fetchFiles.bind(this);
}
componentDidMount() {
this.fetchFiles();
}
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
The idea is to get all items from Dropbox using it's API (JavaScript SDK)
and then for each item I also need to call different API endpoint to get a temporary download link and assign it as a new property. Only after all items will get their links attached I want to setState and render the component. Could somebody please help with this, I spend already multiple hours fighting with promises :S
You could use Promise.all to wait for multiple promises. Also keep in mind that setState is async and you wont see immediate changes. You need to pass a callback.
fetchFiles() {
axios.get('/list')
.then((response) => {
var items = response.data.entries;
// wait for all nested calls to finish
return Promise.all(items.map((item, index) => {
return axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
return item
});
}));
})
.then(items => this.setState(prevState => ({
isLoaded: true,
items: items
}), () => console.log(this.state.items)))
.catch((error) => {
console.log(error);
})
}
Try making the fetchfiles() function as an asynchronous method by adding the async keyword.Now, we have to wait till the items to get their download link, so add a await keyword before that line which makes the code to wait till the axios call gets completed.
async function fetchFiles() {
axios.get('/list')
.then(async function(response){
var items = response.data.entries;
await items.forEach((item, index) => {
axios.get('/download'+ item.path_lower)
.then((response) => {
item.link = response.data;
})
.catch(error => {
console.log(error);
})
});
this.setState(prevState => ({
isLoaded: true,
items: items
}));
console.log(this.state.items);
})
.catch((error) => {
console.log(error);
})
}
I haven't tested the code, but it should probably work.