Chaining Fetch Calls React.js - javascript

I'm making an application where I have to grab certain data from the Github API. I need to grab the name, url, language and latest tag. Because the latest tag is in a separate url, I need to make another fetch call there to grab that data.
I'm running into a certain amount of errors.
1st being the typeError cannot read property 'name' of undefined. I'm sure this is from the fetch call to the tag url where there isn't any data. I'm not really sure how to check if it's undefined. I've tried calling checking to see if the typeof data is undefined and so on but still get the error.
2nd problem being my tag url data doesn't show up with the other data. I'm sure I'm chaining the data wrong because when I click the add button it shows up.
Here is my code:
import React, { Component } from 'react'
import './App.css'
class App extends Component {
state = {
searchTerm: '',
repos: [],
favourites: []
}
handleChange = e => {
const { searchTerm } = this.state
this.setState({ searchTerm: e.target.value })
if (searchTerm.split('').length - 1 === 0) {
this.setState({ repos: [] })
}
}
findRepos = () => {
const { searchTerm } = this.state
// First api call here
fetch(`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${process.env.REACT_APP_TOKEN}
`)
.then(res => res.json())
.then(data => {
const repos = data.items.map(item => {
const { id, full_name, html_url, language } = item
const obj = {
id,
full_name,
html_url,
language,
isFavourite: false
}
// Second api call here. I need the data from map to get the tags for the correct repo
fetch(`https://api.github.com/repos/${full_name}/tags`)
.then(res => res.json())
.then(data => {
obj.latest_tag = data[0].name
})
.catch(err => console.log(err))
return obj
})
this.setState({ repos })
})
.catch(err => console.log(err))
}
render() {
const { searchTerm, repos, favourites } = this.state
return (
<div className="App">
<h1>My Github Favorites</h1>
<input
type="text"
placeholder="search for a repo..."
value={searchTerm}
onChange={e => this.handleChange(e)}
onKeyPress={e => e.key === 'Enter' && this.findRepos()}
/>
<button
type="submit"
onClick={this.findRepos}>
Search
</button>
<div className="category-container">
<div className="labels">
<h5>Name</h5>
<h5>Language</h5>
<h5>Latest Tag</h5>
</div>
// Here I list the data
{repos.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
{repo.latest_tag ? <p>{repo.latest_tag}</p> : <p>-</p>}
<button onClick={() => this.addToFavs(repo)}>Add</button>
</div>
))}
<h1>Favourites</h1>
{favourites.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>{repo.full_name}</a>
<p>{repo.language}</p>
<p>{repo.latest_tag}</p>
<button>Remove</button>
</div>
))}
</div>
</div>
)
}
}
export default App

If you use Promise.all(), you could rewrite your code like the following.
findRepos = () => {
const { searchTerm } = this.state;
// First api call here
const first = fetch(
`https://api.github.com/search/repositories?q=${searchTerm}&per_page=10&access_token=${
process.env.REACT_APP_TOKEN
}`
);
// Second api call here. I need the data from map to get the tags for the correct repo
const second = fetch(`https://api.github.com/repos/${full_name}/tags`);
Promise.all([first, second])
.then((res) => Promise.all(res.map(r => r.json())))
.then([data1, data2] => {
data1.then((firstData) => {
/*Do something you want for first.*/
});
data2.then((secondData) => {
/*Do something you want for second.*/
});
})
.catch((err) => console.log(err));
};
Hope this works for you.

Related

Redux store is updated but view is not

I have the parent Posts.js component which map every object in posts array. In this function I try to filter all notes have same post_id as id of the current mapped post object. All stored in filteredNotes variable. Then I pass it to each child. Now the issue. When I want to add new note in specific post, the view doesn't update (new note was not added to the list) although the database and redux store has been updated successfully.
But when I try to remove that filter function, everything works just fine so I guess the main problem is there. Any idea how to fix this? Thanks
Posts.js
const posts = useSelector((state) => state.post.posts);
const notes = useSelector((state) => state.notes.notes);
useEffect(() => {
dispatch(getPosts());
dispatch(getNotes());
}, []);
const addNoteHandle = (val) => {
dispatch(addNote({new_note: val}));
}
return (
<div className="post__page">
<div className="post__list">
{posts.map((data) => {
let filteredNotes = notes.filter((i) => i.post_id === data.id);
return <Post data={data} notes={filteredNotes} />;
})}
</div>
<PostForm addNewNote={addNoteHandle} />
</div>
);
Post.js
export const Post = ({ data, notes }) => {
return (
<div className="post__item">
<div className="post__title">{data.title}</div>
<div className="post__note">
{notes.map(note => <div>{note.text}</div>)}
</div>
</div>
);
};
NoteForm.js
const NoteForm = ({ addNewNote }) => {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={(e) => setText(e.target.value)} />
<Button type="primary" onClick={() => addNewNote(text)} >
<SendOutlined />
</Button>
</div>
);
};
Action
export const addNote = ({ new_note }) => async (dispatch) => {
try {
const res = await axios.post("http://localhost:9000/api/note", new_note);
dispatch({ type: ADD_NOTE, payload: res.data });
} catch (err) {
dispatch({ type: NOTE_FAIL });
}
};
Reducer
case ADD_NOTE:
return {
...state,
notes: [...state.notes, payload]
};
use useSelector to get the component value from redux store. for some reason hook setText will not work to update the page component. I had a similar problem and could not find any solution. This code may help:
let text ='';
text = useSelector((state) =>
state.yourReducer.text);
Now show your text wherever you want
this will fix the issue until you find real solution

React : Empty values on PUT for axios

I have a simple list that I get from an API using axios.
Every element is a modifiable input, with it own update button.
After changing the data of an input, and while performing PUT request, console.log(test); returns empty values.
I checked console.log(newList); which is the array of the list, and the changing data are indeed happening in the list, but it seems they can't be sent to the server.
Note : The API is just for testing, the PUT method may not work, but atleast the values in the console should be sent.
Note2 : I don't know how to place the id of an item of the list in the url so you may encounter an error. / You can try with 1,2 or 3 instead for testing.
https://codesandbox.io/s/quizzical-snowflake-dw1xr?file=/src/App.js:1809-1834
import React, { useState, useEffect } from "react";
import axios from "axios";
export default () => {
const [list, setList] = React.useState([]);
const [name, setName] = React.useState("");
const [description, setDescription] = React.useState("");
const [city, setCity] = React.useState("");
// Getting initial list from API
useEffect(() => {
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((response) => {
setList(response.data);
console.log(response);
})
.catch((err) => console.log(err));
}, []);
// onUpdate to update the data in the API
const onUpdate = (e) => {
e.preventDefault();
const test = {
name: name,
description: description,
city: city
};
console.log(test);
// axios request PUT data on API
axios
.put(
"https://6092374385ff5100172122c8.mockapi.io/api/test/users" + id,
test
)
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
// axios request GET to get the new modified list from the database, after the update
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
};
// Handler for changing values of each input
function handleChangeUpdate(id, event) {
const { name, value } = event.target;
const newList = list.map((item) => {
if (item.id === id) {
const updatedItem = {
...item,
[name]: value
};
return updatedItem;
}
return item;
});
setList(newList);
console.log(newList);
}
return (
<div>
<ul>
<div>
{list.map((item) => (
<li key={item.id}>
<input
className="form-control"
name="name"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.name}
></input>
<input
className="form-control"
name="description"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.description}
></input>
<input
className="form-control"
name="city"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.city}
></input>
<button onClick={onUpdate}>Update</button>
</li>
))}
</div>
</ul>
</div>
);
};
It's because you never set the values of the props. That is why they never change from their initial values. You just update the list prop in handleChangeUpdate. There are two steps you need to take with the existing file structure:
Make handleChangeUpdate be able to differentiate between different props (city, description, etc.). For example, by passing the prop's name.
Update the prop's value in the handleChangeUpdate.
To realize the first step, you can change the input tag like the following:
{/* attention to the first argument of handleChangeUpdate */}
<input
className="form-control"
name="name"
onChange={(event) => handleChangeUpdate("name", item.id, event)}
defaultValue={item.name}
></input>
Then, you need to adjust the handleChangeUpdate:
if (name === "name") {
setName(value);
} else if (name === "description") {
setDescription(value);
} else if (name === "city") {
setCity(value);
}
By the way, list is not a good name for a variable.
Alternatively
Without creating new parameters, you can also use only the event to set the props
// Handler for changing values of each input
function handleChangeUpdate(id, event) {
const { name, value } = event.target;
const newList = list.map((item) => {
if (item.id === id) {
const updatedItem = {
...item,
[name]: value
};
return updatedItem;
}
return item;
});
setList(newList);
console.log(newList);
if (name === "name") {
setName(value);
} else if (name === "description") {
setDescription(value);
} else if (name === "city") {
setCity(value);
}
}
I think you have 3 errors in the onUpdate function.
You are not passing the id of the item from the onClick event
Your put method should be change
You should not perform get request as soon as after the put request, because sometimes the backend will not updated yet.
You can update your code as below,
1.Pass the id of the item when the button is clicked.
<button onClick={onUpdate(item.id)}>Update</button>
Modify the put method, passing the id
axios
.put(
`https://6092374385ff5100172122c8.mockapi.io/api/test/users/${e}`,
test
).then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
3.Perform the get request after the response of the put request
const onUpdate = (e) => {
const test = {
name: name,
description: description,
city: city
};
console.log(test);
// axios request PUT data on API
axios
.put(
`https://6092374385ff5100172122c8.mockapi.io/api/test/users/${e}`,
test
)
.then((res) => {
console.log(res);
// axios request GET to get the new modified list from the database, after the update
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
};

Search over json object (react)

I am trying to have a search bar which updates the set of json entries depending upon query. Here's the code that displays the video list (<Videos videos={this.state.data}/>). When the page loads, I want to call <Videos videos={this.state.data}/>, but after this query from search bar should update this list. My search functionality is not working for some reason.
class App extends Component {
state = {
query: "",
data: [],
filteredData: []
};
handleInputChange = event => {
const query = event.target.value;
this.setState(prevState => {
const filteredData = prevState.data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
return {
query,
filteredData
};
});
};
getData = () => {
fetch('http://localhost:3000/api/videos')
.then(response => response.json())
.then(data => {
const { query } = this.state;
const filteredData = data.filter(element => {
return element.title.toLowerCase().includes(query.toLowerCase());
});
this.setState({
data,
filteredData
});
});
};
componentWillMount() {
this.getData();
}
render() {
return (
<div className="searchForm">
<form>
<input
placeholder="Search for..."
value={this.state.query}
onChange={this.handleInputChange}
/>
</form>
<Videos videos={this.state.data}/>
<div>{this.state.filteredData.map(i => <p>{i.name}</p>)}</div>
</div>
);
}
}
I am new to react, any pointers will be appreciated.
You are creating a new object without the data property and setting that object as the state whenever you are calling this.setState. So the data property is getting deleted.
On the handleInputChange method do this:
return {
...this.state,
query,
filteredData
};
And on the getData method do this:
this.setState({
...this.state,
data,
filteredData
});
You are using this.state.data in the Videos component which you got from the server. You should use this.state.filteredData to show entries depend on query:
<Videos videos={this.state.filteredData}/>
You need to bind the handleInputChange to this.
Look at this for more on this.

Re render React Native Flatlist on data fetch from API

I am working on a React Native app where i'm using Flatlist.I have a list to render.But i want to add items to the list which i'm getting from the API on button click. I can see my API data on the console but they are not rendering for some reason. Also the list page i'm talking about is a child component. Here's what it looks like:
class ProductList extends Component {
state = {
isSpinner: false,
newArr: []
};
onScrollEndDrag = async() => {
this.setState({ isSpinner: true });
return await fetch(
`myAPI`
)
.then(response => response.json())
.then(json => {
this.setState({ newArr: [...this.state.newArr, ...(json || [])] })
return json;
})
.catch(error => console.log(error));
}
this.setState({ isSpinner: false });
}
render(){
const list = [data1,data2,data3];
return(
<Fragment>
<FlatList
key={this.key}
data={[...list,...this.state.newArr]}
renderItem={this.renderItem}
/>
<Button
title='Load More'
onPress={this.onScrollEndDrag}>
</Button>
</Fragment>
)
}
}
What can be done to show the new API data with the existing list?
Since you are anyways using async await no need of again using .then and .catch instead you can do it the below way.
onScrollEndDrag = async() => {
this.setState({ isSpinner: true });
try {
const response = await fetch(`myAPI`);
const json = await response.json();
this.setState({
newArr: [...this.state.newArr, ...(json || [])],
isSpinner: false
})
} catch(error) {
console.log(error);
this.setState({ isSpinner: false });
}
}
Hope this helps.

How to display multiple sets of data with reduce and promise.all in React

I'm making two calls from an API. I want to display the top results for airing shows and top tv shows. I have all of the data being returned from both API calls, but my code isn't efficient. I'd like to somehow take my returned data and display it in a single component (TopAnime) that will then map and return the information provided.
I figured reduce would be the best route, but I'm fumbling at this point. My thought process was to reduce the returned data from the API into an array. Take that reduced array and pass it as my new state and then have my component display it without having to write duplicate code. Both topTv and topAIring are showing because I've written the component twice, but it's clearly not best practice to repeat code.
class HomePage extends Component {
state = {
topTv: [],
topAiring: []
}
async getData() {
const api = "https://api.jikan.moe/v3"
const urls = [
`${api}/top/anime/1/tv`,
`${api}/top/anime/1/airing`
];
return Promise.all(
urls.map(async url => {
return await fetch(url) // fetch data from urls
})
)
.then(responses => // convert response to json and setState to retrieved data
Promise.all(responses.map(resp => resp.json())).then(data => {
console.log("data", data)
// const results = [...data[0].top, ...data[1].top]; // data from TV & data from airing
const reduceResults = data.reduce((acc, anime) => {
return acc + anime
}, [])
console.log('reduce', reduceResults);
const tvResults = data[0].top // data from TV
const airingResults = data[1].top // data from airing
this.setState({
topTv: tvResults,
topAiring: airingResults
});
})
)
.catch(err => console.log("There was an error:" + err))
}
componentDidMount() {
this.getData();
}
render() {
return (
<HomeWrapper>
<h2>Top anime</h2>
<TopAnime>
{this.state.topTv.map((ani) => {
return (
<div key={ani.mal_id}>
<img src={ani.image_url} alt='anime poster' />
<h3>{ani.title}</h3>
</div>
)
}).splice(0, 6)}
</TopAnime>
<h2>Top Airing</h2>
<TopAnime>
{this.state.topAiring.map((ani) => {
return (
<div key={ani.mal_id}>
<img src={ani.image_url} alt='anime poster' />
<h3>{ani.title}</h3>
</div>
)
}).splice(0, 6)}
</TopAnime>
</HomeWrapper>
)
}
}
Since the response from API contains a flag called rank you can use the Array.prototype.filter to only show shows ranked 1-6.
Working demo here
import React, { Component } from "react";
import { TopAnime } from "./TopAnime";
export class HomePage extends Component {
state = {
topTv: [],
topAiring: []
};
async getData() {
const api = "https://api.jikan.moe/v3";
const urls = [`${api}/top/anime/1/tv`, `${api}/top/anime/1/airing`];
return Promise.all(
urls.map(async url => {
return await fetch(url); // fetch data from urls
})
)
.then((
responses // convert response to json and setState to retrieved data
) =>
Promise.all(responses.map(resp => resp.json())).then(data => {
// if you care about mutation use this
const topTvFiltered = data[0].top.filter( (item) => item.rank <= 6 );
const topAiringFiltered = data[1].top.filter( (item) => item.rank <= 6 );
this.setState({
topTv: topTvFiltered,
topAiring: topAiringFiltered
});
})
)
.catch(err => console.log("There was an error:" + err));
}
componentDidMount() {
this.getData();
}
render() {
const { topTv, topAiring } = this.state;
return (
<React.Fragment>
{ topTv.length > 0 ? <h2>Top TV</h2> : null }
{this.state.topTv.map((item, index) => (
<TopAnime key={index} title={item.title} image={item.image_url} />
))}
{ topAiring.length > 0 ? <h2>Top airing</h2> : null }
{this.state.topAiring.map((item, index) => (
<TopAnime key={index} title={item.title} image={item.image_url} />
))}
</React.Fragment>
);
}
}

Categories