Reloading the page loses API data using useContext - reactjs

I am using a RapidAPI Api to load crypto currency data in my project. The data is loading and even rendering in my React components but as soon as I refresh, I have to load the data from the beginning to get to specific coin data. On reload, I get TypeError: Cannot read properties of undefined (reading 'name')
Here is my code:
import React, { useState, useEffect } from "react";
import "./Homepage.css";
import CryptoCard from "../Card/Card";
import axios from "axios";
const Homepage = () => {
const [coinData, setCoinData] = useState([]);
useEffect(() => {
const i = 5;
const options = {
method: "GET",
url: "https://coinranking1.p.rapidapi.com/exchanges",
headers: {
"x-rapidapi-host": "coinranking1.p.rapidapi.com",
"x-rapidapi-key": "REDACTED",
},
};
axios
.request(options)
.then((response) => {
setCoinData(response.data.data.exchanges);
// console.log(coinData);
// console.log("Working!!");
})
.catch((error) => {
console.error(error);
});
}, []);
return (
<div className="homepage">
<div className="heading">
<h1>Discover {coinData[0].name}</h1>
<hr className="line" />
</div>
<div className="cards-container">
<CryptoCard />
</div>
</div>
);
};
export default Homepage;
Why am I getting

Reason for your error message
coinData[0] does not exist when rendering the component initially. You've defined it as useState([]), so every time the component gets created, you start with a fresh empty array. Therefore, you should add a check, if you got some data in it.
<h1>Discover {coinData.length > 0 && coinData[0].name}</h1>
Reason for refetch
Your useEffect will be executed once when the component gets rendered. You make the request and put the data in the coinData state. But the state is not persistent. You could use the local storage to cache your request across page refresh. To do this, you need to persist the data when your request finishes and load the data when you create your state.
const [coinData, setCoinData] = useState([], () => {
const localData = localStorage.getItem('coinData');
return localData ? JSON.parse(localData) : [];
});
useEffect(() => {
const i = 5;
const options = {
method: "GET",
url: "https://coinranking1.p.rapidapi.com/exchanges",
headers: {
"x-rapidapi-host": "coinranking1.p.rapidapi.com",
"x-rapidapi-key": "REDACTED",
},
};
axios
.request(options)
.then((response) => {
setCoinData(response.data.data.exchanges);
// console.log(coinData);
// console.log("Working!!");
// persist in localStorage
localStorage.setItem("coinData", JSON.stringify(response.data.data.exchanges))
})
.catch((error) => {
console.error(error);
});
}, []);
EDIT: This will still make a request every time you hit refresh, but I guess this code will make it clear how it works. So I guess you'll be able to add an if-condition, if you got some data already and skip the new request ;-)

Related

React and Websocket messaging to server

So I'm having issues with a single component that displays a list pulled from a resource server. Then it uses Stompjs to establish a websocket and send messages. When I load the client, the Dev Console shows logs that it tries to call onConnected method() twice as my logs show two newUser messages sent from a single load of the component.
When I try to call the submitBid() method it throws a type error saying that
"TypeError: Cannot read properties of undefined (reading 'send')
at submitBid (AuctionList.js:76:1)"
Which I'm not sure why it would be undefined on that line when it's defined and running fine in the function on line 36 which runs before the method that fails. I've been stuck on this for several days so hopefully someone can tell me what I've got wrong in the code... Here is the component code....
import React from 'react'
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import {useState, useEffect } from 'react';
function AuctionList({user, authCredentials, token}) {
const [auctionItems, setAuctionItems] = useState([]);
const [userData, setUserData] = useState({
email: user.email,
name: user.name,
message: ''
});
const [bid, setBid] = useState(0.00);
let stompClient;
let socket;
const connect = async () => {
socket = new SockJS('http://localhost:8080/ws')
stompClient = Stomp.over(socket)
stompClient.connect({}, onConnected, onError)
}
const onConnected = async () => {
stompClient.subscribe('/topic/bids', onMessageReceived)
stompClient.send("/app/socket.newUser",
{},
JSON.stringify({
sender: user.name,
type: 'NEW_USER',
time: Date.now()
})
)
}
const onError = async (err) => {
console.log(err);
}
const handleChange = async (e) =>{
setBid(e.target.value);
}
const submitBid = async (item) => {
let newMessage = {
type: "BID",
newBid: {
itemId: item.id,
email: user.email,
bidPrice: bid,
bidTime: new Date().getTime()
},
sender: userData.email,
time: new Date().getTime()
};
try {
stompClient.send("/socket.send", {}, JSON.stringify(newMessage));
} catch(err){
console.log(err); }
}
const onMessageReceived = async (payload)=>{
console.log("onMessageReceived")
console.log(payload)
}
const getAuctionList = async () => {
const url = "http://localhost:8080/auctionlist";
const init = {
method: "GET",
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`, // notice the Bearer before your token
},
};
fetch(url, init)
.then(response => response.json())
.then(response => {setAuctionItems(response)})
};
useEffect( () => {
getAuctionList();
connect();
}, []);
return (
<ul>
{auctionItems.map( item => {
return( <div key={item.id} className = "auctionItemComponent">
<h3>{item.name}</h3>
<span>{item.desc}</span>
<span>Current Bid: ${item.itemStartingPrice}</span>
<span>Minimum Bid: {item.itemMinBid}</span>
<span>Time left</span>
<input type="number" id="bidInput_" name="bidInput" onChange={handleChange} ></input>
<button type='submit' onClick={submitBid}>Submit bid</button>
</div>)
})}
</ul>
)
}
export default AuctionList
Also I realize I have a bunch of async functions that don't have any awaits. I tried adding those in, but it was no change.
The issue here is not with stompjs but with the scoping. You have stompClient inside React Component but the one from submitBid is different. You can do it in different ways.
Put stompjs in global stage as in example here: https://playcode.io/972045
You can use useRef to have the client inside the React Component and have React do the tracking of any modifications.
I personally think something like a "connection" should stay away from inside a React Component. You should have the connection configs in a different file and import an instance to the JSX file.

best way to authenticate with SWR (firebase auth)

I'm doing project with React , firebase auth social signin(google, github provider) and backend(spring boot)
I'm wondering how can i use useSWR for global state for google userData
Here's my Code This is Login page simply i coded
In this page, I fetch userData(email, nickname ,, etc) with header's idToken(received from firebase auth) and backend validates idToken and send me a response about userData
This is not problem I guess.. But
// import GithubLogin from '#src/components/GithubLogin';
import GoogleLogin from '#src/components/GoogleLogin';
import { auth, signOut } from '#src/service/firebase';
import { fetcherWithToken } from '#src/utils/fetcher';
import React, { useEffect, useState } from 'react';
import useSWR from 'swr';
const Login = () => {
const [token, setToken] = useState<string | undefined>('');
const { data: userData, error } = useSWR(['/api/user/me', token], fetcherWithToken);
useEffect(() => {
auth.onAuthStateChanged(async (firebaseUser) => {
const token = await firebaseUser?.getIdToken();
sessionStorage.setItem('user', token!);
setToken(token);
});
}, []);
return (
<div>
<button onClick={signOut}>Logout</button>
<h2>Login Page</h2>
<GoogleLogin />
</div>
);
};
export default Login;
Here's Code about fetcher using in useSWR parameter
export const fetcherWithToken = async (url: string, token: string) => {
await axios
.get(url, {
headers: {
Authorization: `Bearer ${token}`,
Content-Type: 'application/json',
},
withCredentials: true,
})
.then((res) => res.data)
.catch((err) => {
if (err) {
throw new Error('There is error on your site');
}
});
};
problem
I want to use userData from useSWR("/api/user/me", fetcherWithToken) in other page! (ex : Profile Page, header's Logout button visibility)
But for doing this, I have to pass idToken (Bearer ${token}) every single time i use useSWR for userData. const { data: userData, error } = useSWR(['/api/user/me', token], fetcherWithToken);
Like this.
What is the best way to use useSWR with header's token to use data in other pages too?
seriously, I'm considering using recoil, context api too.
but I don't want to.
You can make SWR calls reusable by wrapping them with a custom hook. See the SWR docs page below.
Make It Reusable
When building a web app, you might need to reuse the data in many
places of the UI. It is incredibly easy to create reusable data hooks
on top of SWR:
function useUser (id) {
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
return {
user: data,
isLoading: !error && !data,
isError: error
}
}
And use it in your components:
function Avatar ({ id }) {
const { user, isLoading, isError } = useUser(id)
if (isLoading) return <Spinner />
if (isError) return <Error />
return <img src={user.avatar} />
}

Axios get request returns undefined for the first time in React

I am using DRF for creating the api.. I am able to fetch the data using axios, but it returns undefined the first time and hence, when I use useState, it gets set as undefined..
ItemDetail.js:
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const ItemDetail = () => {
const [detail, setDetail] = useState('')
const [id, setId] = useState('')
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: `http://127.0.0.1:8000/api/recipe-detail/1`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
}
})
setDetail(res.data)
}
useEffect(() => {
RecipeDetail()
}, [id])
console.log(`Hi ${detail}`)
return (
<div>
Hi
</div>
)
}
export default ItemDetail
So why is the API returning undefined for the first time?
I read few answers regarding the use of async/await which I have.. Also, when I console.log(detail), it logs it multiple times.. Why is that so?
As u can see in the image, it logs multiple times..
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ItemDetail = () => {
const [detail, setDetail] = useState('');
const RecipeDetail = async () => {
const res = await axios({
method: 'get',
url: 'http://127.0.0.1:8000/api/recipe-detail/1',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Access-Control-Allow-Origin': '*',
},
});
setDetail(res.data);
};
useEffect(() => {
RecipeDetail();
}, [detail]);
console.log(`Hi ${detail}`);
return (
<div>
{detail ? 'Hi' : 'Loading ... '}
</div>
);
};
export default ItemDetail;
You are trying to access or log the detail before it is set ,means that useEffect will be called after your content is rendered so console.log(Hi ${detail}); ran first and undefined was logged , later useEffect ran and RecipeDetail(); data received state changed as you called setState. and rerender occured again and this time you received value.
When it was first rendered, no API response was received yet.
and then, when second rendered, API resonse was receiced.

useEffect not triggering in ContextProvider

I am using useEffect hook inside an Context Provider called DataProvider. In the useEffect hook I am sending a request to a endpoint using axios. But the thing is, the useEffect hook not triggering. What Should I do in this situation. Can anyone help me on this issue.
Context Provider code:
export const DataProvider = ({ children }) => {
const [data, setData] = useState({});
useEffect(() => {
const options = {
method: "GET",
url: "https://corona-virus-world-and-india-data.p.rapidapi.com/api_india",
headers: {
"x-rapidapi-key": API_KEY,
"x-rapidapi-host": API_HOST
}
};
axios
.request(options)
.then(function(response) {
console.log(response.data);
setData(response.data);
})
.catch(function(error) {
console.error(error);
});
}, []);
if(data) {
return (
<DataContext.Provider value={[data, setData]}>
{children}
</DataContext.Provider>
);
} else {
console.log("error");
}
};
And below is the error image
Please help me to fix this error 🙏.
React renders first and only after first render it executes the useEffect. You're trying to access a property that is not yet initialized.
Just handle your data and render conditionally with if or do this in your card Container:
<Cardcontainer caseNumber={numberWithCommas(data?.total_values?.deaths)}/>
Make sure numberWithCommas knows how to handle an undefined parameter.

Error: Can't perform a React state update on an unmounted component

I need your help.
I have a component that retrieves data from a REST and when the response returns, I enable the button for the PDF.
const [pdf, setPDF] = useState(false);
const [utente, setUtente] = useState(null);
useEffect(() => {
const url = ""
axios.get(url, {
params: {
id: props.id
}
})
.then((response) => {
setPDF(true);
setUtente(response);
})
.catch((err) => {
setPDF(false);
setUtente(null);
});
return () => {
};
}, [props.id]);
return (
<div className="container">
{
loadPDF
?
<PDFDownloadLink document={<ComponentPDF utente={utente} />} fileName="utente.pdf">
{ <img src="pdf.png" title="PDF" alt="PDF" /> }
</PDFDownloadLink>
:
<React.Fragment/>
}
</div >
);
it works well, but if I go back to the home, sometimes I get this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in InternalBlobProvider (created by PDFDownloadLink)
can someone help me?
I tried the solutions you indicated, but I still have the same error.
My "loadPDF" value is true, this is because I received the response from AXIOS.
If after receiving the response from AXIOS I wait a few seconds and then i go back with browser, I don't have this error.
if after the AXIOS reply I go back with the browser, I received this and error.
this is because inside Component PDF there is a lot of logic and maybe it takes some time.
do I have to work in ComponentePDF?
What's happening is that React tries to update state when the component is unmounted, so we need to cancel the Axios request when the component unmounts.
I'm gonna destructure id from props for simplicity. Also, you can check Cancellation docs from Axios on how to do it, but I'll leave you the following example:
useEffect(() => {
const url = '';
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios
.get(url, {
params: { id },
cancelToken: source.token,
})
.then((response) => {
setPDF(true);
setUtente(response);
})
.catch((err) => {
setPDF(false);
setUtente(null);
});
return () => source.cancel();
}, [id]);

Resources