Problem fetching data from Axios in React - javascript

I'm creating a search that will print out results from the following API: https://jsonplaceholder.typicode.com/users.
At this stage I just want the data to print out as search results. Currently, the "Failed to fetch results. Please check network" error message displays after any search.
Here's my search component:
import React from "react";
import "../styles.css";
import axios from "axios";
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = "";
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
});
};
handleOnInputChange = event => {
const query = event.target.value;
if (!query) {
this.setState({ query, results: {}, message: "" });
} else {
this.setState({ query, loading: true, message: "" }, () => {
this.fetchSearchResults(1, query);
});
}
};
renderSearchResults = () => {
const { results } = this.state;
if (Object.keys(results).length && results.length) {
return (
<ul>
{results.map(result => (
<li>{result.name}</li>
))}
</ul>
);
}
};
render() {
const { query, message } = this.state;
return (
<div className="container">
{/*Heading*/}
<h2 className="heading">Live Search: React Application</h2>
{/*Search Input*/}
<label className="search-label" htmlFor="search-input">
<input
type="text"
value={query}
id="search-input"
placeholder="Search..."
onChange={this.handleOnInputChange}
/>
<i className="fa fa-search search-icon" />
</label>
{/* Error Message*/}
{message && <p className="message">{message}</p>}
{/*Result*/}
{this.renderSearchResults()}
</div>
);
}
}
export default Search;

The reason why the code fails is in the target searchUrl.
A quick look and I can see that the searchUrl that is formed when the user types "Tim" is:
https://jsonplaceholder.typicode.com/userstim&page=1
If you look at the HTTP request there's an 404 error:
GET https://jsonplaceholder.typicode.com/userstim&page=1
[HTTP/2 404 Not Found 18ms]
So, have in mind that you should always look into the original error message, you can of course present a different message to the end user, but this would have been helpful to you:
.catch(error => {
console.log("error: ", error.message);
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
});
So, the reason why this is not working is the searchUrl pointing to an unexisting endpoint location. You can simply remove the query and see it in action!
const searchUrl = `https://jsonplaceholder.typicode.com/users`;
So, fix the searchUrl and check the API documentation to understand what to do to filter by username.
It's out of the scope for the question but you can filter the data after the request to /users if a way to fetch by name doesn't exist...
data.filter(item => item.username === query)

You need to set this.cancel to undefined when you initialise it, and after a search. Something like this:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = undefined;
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
this.cancel = undefined;
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
this.cancel = undefined;
});
};
But it's better to do it in one place:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = undefined;
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
})
.finally(() => {this.cancel = undefined})
};
If your environment supports Promise.finally.

Related

Unhandled Rejection (TypeError): info.contract.methods is undefined

Hello, I was wondering if anybody have the following issue(Unhandled Rejection (TypeError): info.contract.methods is undefined):
enter image description here
Here is where info gets setup
const intialInfo = {
connected: false,
status: null,
account: null,
contract: null,
};
const intialDropState = {
loading: false,
list:[],
};
console.log(contract);
const DropList = () => {
const [info, setInfo] = useState(intialInfo);
const [drops,setDrops] = useState(intialDropState);
**My smart contract calls getDrops by doing the following:
//Get the NFT drop objects list
function getDrops() public view returns(Drop[] memory) {
return drops;
}
**
here is the part of the code that has the issue
const getDrops = async() => {
setDrops(prevState => ({
...prevState,
loading: true,
}));
info.contract.methods
.getDrops()
.call()
.then((res) => {
console.log(res);
setDrops({
loading: false,
list: res,
});
})
.catch((err) => {
console.log(err);
setDrops(intialDropState);
});
};
Here is the code
I have taken a picture of all my code in the following photos:
enter image description here
enter image description here
import contract from "../contract/contract.json";
import Web3 from "web3";
import {useState, useEffect} from "react";
const intialInfo = {
connected: false,
status: null,
account: null,
contract: null,
};
const intialDropState = {
loading: false,
list:[],
};
console.log(contract);
const DropList = () => {
const [info, setInfo] = useState(intialInfo);
const [drops,setDrops] = useState(intialDropState);
// connecting to metamask and inital state of web dapp
const init = async() => {
//Connect to blockchain to metamask if there is a metamask
if(window.ethereum.isMetaMask){
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const networkId = await window.ethereum.request({
method: "net_version",
});
//network == 4 for testnet for ETH use networkId == 1
if(networkId === 4){
let web3 = new Web3(window.ethereum);
setInfo({
...intialInfo,
connected: true,
account: accounts[0],
contract: new web3.ethereum.Contract(contract.abi, contract.address),
});
}
else{
setInfo({ ...intialInfo, status: "You need to be on the Ethereum testnet."});
}
}
else{
setInfo({ ...intialInfo, status: "You need metamask."});
}
};
const initOnChange = () => {
if(window.ethereum){
window.ethereum.on("accountsChanged", () =>{
window.location.reload();
});
window.ethereum.on("chainChanged", () =>{
window.location.reload();
});
}
};
const getDrops = async() => {
setDrops(prevState => ({
...prevState,
loading: true,
}));
info.contract.methods
.getDrops()
.call()
.then((result) => {
console.log(result);
setDrops({
loading: false,
list: result,
});
})
.catch((err) => {
console.log(err);
setDrops(intialDropState);
});
};
useEffect(() => {
init();
initOnChange();
}, []);
return (
<div>
<button onClick={() => getDrops()}>Get Drops </button>
{drops.loading ? <p>Loading</p> : null}
</div>
);
};
export default DropList;
I think the issue is with the useEffect
useEffect(() => {
init();
initOnChange();
}, []);
You have to pass dependencies,
useEffect(() => {
init();
initOnChange();
// as far as I see, those are the only dependencies
}, [window.ethereum,info]);
then in jsx to avoid your app crashing:
info && info.contract && info.contract.methods
or depending on your packages, you could use this
info?.contract?.methods

why componentdidmount called two times

I have React Component in componentDidMount fetch data from the server. The issue is componentDidMount called twice also the API called twice. I have a view increment API like youtube video views increment twice in the database because of twice API calling.
class SingleVideoPlay extends React.Component {
constructor(props) {
super(props);
this.player = React.createRef();
}
state = {
autoPlay: true,
relatedVideos: [],
video: null,
user: null,
comments: [],
commentInput: {
value: '',
touch: false,
error: false
},
following: false,
tab: 'comments'
};
_Mounted = false;
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
componentWillUnmount() {
this._Mounted = false;
try {
clearInterval(this.state.videoInterval);
this.props.videoEditUrl('');
} catch (error) {}
}
captureVideoTime = async () => {
const { video } = this.state;
const result = await updateWatchTime({
id: video._id,
time: 1
});
if (result.status === 200) {
const updateVideo = {
...video,
secondsWatched: video.secondsWatched + 1
};
this.setState({ video: updateVideo });
}
};
videoEnded = () => {
clearInterval(this.state.videoInterval);
};
videoPause = () => {
clearInterval(this.state.videoInterval);
};
loadVideo = () => {
clearInterval(this.state.videoInterval);
};
playingVideo = () => {
const interval = setInterval(this.captureVideoTime, 1000);
this.setState({ videoInterval: interval });
};
getVideoDetails = async (videoId) => {
const video = await getVideo(videoId);
if (video.status === 200) {
let response = video.data;
if (this.props.userId)
if (response.user._id === this.props.userId._id)
this.props.videoEditUrl(`/video/edit/${response.media._id}`);
this.setState({
relatedVideos: response.videos.docs,
video: response.media,
user: response.user
});
this.checkIsFollowing();
this.updateVideoStat(response.media._id);
}
};
updateVideoStat = async (id) => videoView(id);
checkIsFollowing = async () => {
const { userId } = this.props;
const { video } = this.state;
if (userId && video) {
const response = await isFollow({
follower: userId._id,
following: video._id
});
if (response) {
this.setState({ following: response.following });
}
}
};
addOrRemoveFollowing = async () => {
this.checkIsFollowing();
const { following, video } = this.state;
const { userId } = this.props;
if (userId) {
if (following) {
const response = await removeFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: false });
} else {
const response = await addFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: true });
}
}
};
submitCommentHandler = async (event) => {
const { userId } = this.props;
event.preventDefault();
if (userId) {
const result = await saveComment({
mediaId: this.state.video._id,
parentId: '0',
userID: userId._id,
userName: userId.username,
comment: this.state.commentInput.value
});
console.log(result);
if (result.status === 200) {
this.getVideoComments();
this.setState({ commentInput: { value: '', touch: false, error: false } });
}
}
};
render() {
const { autoPlay, relatedVideos, video, user, comments, commentInput, following, tab } = this.state;
const { userId } = this.props;
return (
<div className="container-fluid">
some coponents
</div>
);
}
}
const mapStateToProps = (state) => ({
userId: state.auth.user
});
export default connect(mapStateToProps, { videoEditUrl })(SingleVideoPlay);
I don't know why componentDidMount called two times alse it shows memmory lecage issue.
How to Fix it.
Multiple componentDidMount calls may be caused by using <React.StrictMode> around your component. After removing it double calls are gone.
This is intended behavior to help detect unexpected side effects. You can read more about it in the docs. It happens only in development environment, while in production componentDidMount is called only once even with <React.StrictMode>.
This was tested with React 18.1.0
I think the issue exists on the parent component that used SingleVideoPlay component. Probably that parent component caused SingleVideoPlay component rendered more than once.
Also, there is an issue on your code.
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
Here, no need to check if this._Mounted, because it will always be true.
1.Install jQuery by
npm i jquery
import $ from 'jquery'
create your function or jwuery code after the export command or put at the end of the file

Checking firestore real time changes

I am working with firestore real time in a map project, and it requires updating a users current location at x distance interval.
However, the real time listener keeps refetching my own updates, thereby increasing my reads.
I assume firestore real time updates the cache locally before sending to the server, is it possible to ignore fetching changes that are made by that user?
class Booking extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
errorMessage: '',
};
this.unsubscribe = null;
}
componentDidMount() {
this.getRealTimeData();
}
componentWillUnmount() {
this.unsubscribe = null;
}
getRealTimeData = () => {
this.fetchCompletedGigs();
}
fetchCompletedGigs = () => {
const { userID } = this.props;
this.props.bookingData([]);
this.setState({ isLoading: true, errorMessage: '' });
this.unsubscribe = Firebase.shared.fetchBooking('Bookings')
.where('userID', '==', userID)
.orderBy('d.CreatedAt', 'desc')
.limit(20)
.onSnapshot((querySnapshot) => {
if (querySnapshot.empty) {
this.setState({
isLoading: false,
errorMessage: "You currently don't have anybooking",
});
this.props.bookingData([]);
}
querySnapshot.docChanges().forEach(change => {
const doc = change.doc;
const item = doc.data();
item.docId = doc.id;
const list = [...this.props.bookings, item];
this.setState({ isLoading: false, errorMessage: '' });
if (change.type === 'added') {
const filterList = _.uniqBy(list, 'docId');
this.props.bookingData(filterList);
} else if (change.type === 'removed') {
const newData = list.filter(task => task.docId !== doc.id);
const filterList = _.uniqBy(newData, 'docId');
return this.props.bookingData(filterList);
} else if (change.type === 'modified') {
const newData = list.filter(task => task.docId !== doc.id);
const newList = [...newData, item];
const filterList = _.uniqBy(newList, 'docId');
return this.props.bookingData(filterList);
}
}, err => {
this.props.bookingData([]);
console.warn(err);
this.setState({
isLoading: false,
errorMessage: 'Error occurred while fetching your booking',
});
});
}, err => {
this.props.bookingData([]);
console.warn(err);
this.setState({
isLoading: false,
errorMessage: 'Error occurred while fetching your booking.',
});
});
}
You can't prevent the onSnapshot listener from firing for local events. But you can detect those local events inside the callback, and ignore them there:
Firebase.shared.fetchBooking('Bookings')
.where('userID', '==', userID)
.orderBy('d.CreatedAt', 'desc')
.limit(20)
.onSnapshot((querySnapshot) => {
...
querySnapshot.docChanges().forEach(change => {
if (change.doc.metadata.hasPendingWrites) {
... handle the local event differently
}
else {
... handle normally
}
});
...
});
Also see the Firebase documentation on detecting local changes.

mapStateToProps is undefined

I want to fetch data from json file and then render it on the screen with React and Redux. JSX is standart, I used the <Provide> tag and set store value to my store. mapStateToProps is going undefined for this.props as well as toTakeData().
Here I have action file with request:
let data = {
loading: true,
items: [],
prevName: null,
selectedProfile: '',
term: ''
}
export function getItems() {
getRequest();
return {
type: 'GET_ITEMS',
payload: data
}
}
const getRequest = async () => {
const response = await fetch('http://localhost:8000/api/item')
.then( response => response.json() )
.then( json => {
data.items = json;
data.selectedProfile = json[0];
data.loading = false;
data.prevName = json[0].general.firstName + ' ' + json[0].general.lastName;
} )
.catch( err => console.error( err ) );
}
And here is component file which suppose to render data:
const mapStateToProps = state => {
console.log(state.items);
return {
items: state.items,
prevName: state.prevName,
selectedProfile: state.selectedProfile,
term: state.term,
loading: state.loading
};
};
const mapActionsToProps = {
toTakeData: getItems
};
export default connect(mapStateToProps, mapActionsToProps)(SelectMenu);

Repurpose Firebase's Facebook Login ID as a Data Key?

How can I utilize a user's Firebase ID ("id" : "1234567890") as a key for a separate data set within the same db?
the following would be my login detail where I would pull the ID from:
"users": {
"1234567890abcdefghijklmnopqrstuvwxyz" : {
"dp" : "https://www.profilePic.com",
"first_name" : "John",
"id" : "1234567890",
"last_name" : "Doe",
"token" : "abcdefghijklmnopqrstuvwxyz1234567890",
"uid" : "987654321"
}
}
Within Firebase Functions I currently have my code as:
admin.database().ref('location_config/{id}').set({current_location:[34.047220, -118.443534]})
The result currently comes out as:
"location_config": {
"{id}": {
"current_location": [34.047220, -118.443534]
}
}
But this is how I would like the data to appear with the ID being the key:
"location_config": {
"1234567890": {
"current_location": [34.047220, -118.443534]
}
}
The screenshot below illustrates how the UID is dynamic while the ID is constant.
Here is the code within Firebase:
let fbLocation;
module.exports = (event) => {
event.geoFire = functions.database.ref('users').onUpdate(event => {
admin.database().ref('/portal_coordinates_all').once('value', snapshot =>{
fbLocation = snapshot.val();
console.log ("snapshot", fbLocation);
}).then(() => {
// Create a Firebase reference where GeoFire will store its information
let firebaseRef = admin.database().ref('geofire');
// Create a GeoFire index
let geoFire = new GeoFire(firebaseRef);
geoFire.set(fbLocation)
.then(() => {
console.log("Provided key has been added to GeoFire");
}).catch(err => console.log(err))
.then(() => {
let geoQuery = geoFire.query({
center: [34.047220, -118.443534],
radius: 2
});
let locations = [];
let onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
locations.push(location);
});
// fires once when this query's initial state has been loaded from the server.
let onReadyRegistration = geoQuery.on("ready", function() {
console.log("GeoQuery has loaded and fired all other events for initial data");
console.log(locations);
// ******* here is where I'm having the issue *******
admin.database().ref( 'location_config/`${id}`' ).set( {current_location: locations} )
// **************************************************
// Cancel the "key_entered" callback
onKeyEnteredRegistration.cancel();
});
}).catch(err => console.log(err))
})
})
}
And here is the code within React Native:
import React, { Component } from 'react';
import {
StyleSheet,
View,
ActivityIndicator,
Button
} from 'react-native';
import firebase from 'firebase';
import { connect } from 'react-redux';
import { loginSuccess } from '../actions/AuthActions';
const FBSDK = require('react-native-fbsdk');
const { LoginManager, AccessToken } = FBSDK;
class Login extends Component {
constructor(props) {
super(props);
this.state = {
showSpinner: true,
};
}
componentDidMount() {
this.fireBaseListener = firebase.auth().onAuthStateChanged(auth => {
if (auth) {
this.firebaseRef = firebase.database().ref('users');
this.firebaseRef.child(auth.uid).on('value', snap => {
const user = snap.val();
if (user != null) {
this.firebaseRef.child(auth.uid).off('value');
this.props.loginSuccess(user);
}
});
} else {
this.setState({ showSpinner: false });
}
});
}
onPressLogin() {
this.setState({ showSpinner: true })
LoginManager.logInWithReadPermissions([
'public_profile',
'user_birthday',
'email',
'user_photos'
])
.then((result) => this.handleCallBack(result),
function(error) {
alert('Login fail with error: ' + error);
}
);
}
handleCallBack(result) {
let that = this;
if (result.isCancelled) {
alert('Login canceled');
} else {
AccessToken.getCurrentAccessToken().then(
(data) => {
const token = data.accessToken
fetch('https://graph.facebook.com/v2.8/me? fields=id,first_name,last_name&access_token=' + token)
.then((response) => response.json())
.then((json) => {
const imageSize = 120
const facebookID = json.id
const fbImage = `https://graph.facebook.com/${facebookID}/picture?height=${imageSize}`
this.authenticate(data.accessToken)
.then(function(result) {
const { uid } = result;
that.createUser(uid, json, token, fbImage)
});
})
.catch(function(err) {
console.log(err);
});
}
);
}
}
authenticate = (token) => {
const provider = firebase.auth.FacebookAuthProvider;
const credential = provider.credential(token);
return firebase.auth().signInWithCredential(credential);
}
createUser = (uid, userData, token, dp) => {
const defaults = {
uid,
token,
dp
};
firebase.database().ref('users').child(uid).update({ ...userData, ...defaults });
}
render() {
return (
this.state.showSpinner ? <View style={styles.container}><ActivityIndicator animating={this.state.showSpinner} /></View> :
<View style={styles.container}>
<Button
onPress={this.onPressLogin.bind(this)}
title="Login with Facebook"
color="#841584"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
const mapStateToProps = (state) => {
console.log('mapStateToProps', state);
return {
logged: state.auth.loggedIn,
user: state.auth.user
};
};
export default connect(mapStateToProps, { loginSuccess })(Login);
After banging my head against the wall as to why I couldn't grab the Facebook ID, it turned out that there isn't a need to grab this ID since Firebase's UID stays constant. I was unaware that the Firebase ID didn't change because in my test environment I would always login resulting in a new UID being created.

Categories

Resources