Calling an Async function in React without clicking a button? - reactjs

I'm working on a Spotify integration app, and I have the following code within the main function of the App. This code basically lets you log in through Spotify, and then upon being redirected to my app's page, you click a button that gets your top artists for a given time period from your Spotify data. I have included the code that I have a question about below:
***SOME CODE***
const getTopTracks = async (e) => {
e.preventDefault()
const {data} = await axios.get("https://api.spotify.com/v1/me/top/artists", {
headers: {
Authorization: `Bearer ${token}`
},
params: {
limit: 10,
time_range: "short_term"
}
})
console.log(data);
setArtists(data.items);
}
***SOME MORE CODE***
return (
<div className="App">
<header className="App-header">
<h1>monk<span className='smaller'>media</span>
</h1>
{!token ?
<a href={`${AUTH_ENDPOINT}?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=${RESPONSE_TYPE}&scope=${SCOPE}`}>Login to Spotify</a>
: <button className='logout' onClick={logout}> Logout </button>}
{token ?
<form onSubmit={getTopTracks}>
<input type="text" onChange={e => setSearchKey(e.target.value)}/>
<button type={"submit"}>GET MY TOP ARTISTS</button>
</form>
: <h2>Please login ^</h2>
}
{renderArtists()} //prints artists
</header>
</div>
);
}
I had followed a tutorial to create an app that allows you to search for artists through the Spotify Api, so I used some of that code here, which is why there is a form and a button to get your top artists. However, I'd like top artists to appear upon being redirected to my app after successfully logging in through Spotify, and I don't want there to be a button you have to push.
I tried removing the form, and just calling getTopTracks in the return statement, but then the Spotify Api call wouldn't work. I think it has something to do with the async function, but I'm not sure how to solve this problem. How can I remove the form and make it so the "getTopTracks" function is called immediately upon returning to my web app after logging in?
Any help would be appreciated!

You can call useEffect for this:
useEffect(() => {
const getTopTracks = async () => {
const { data } = await axios.get(
"https://api.spotify.com/v1/me/top/artists",
{
headers: {
Authorization: `Bearer ${token}`,
},
params: {
limit: 10,
time_range: "short_term",
},
}
);
console.log(data);
setArtists(data.items);
};
getTopTracks();
}, []);
I also highly recommend reading this article.

Related

Conditional Routing in React based on API calls

So I'm trying to create a React web app with multiple pages and connecting it to Flask to fetch data using the fetch API. Here is what I want to achieve:
If the user submits a Form, React does a POST request to the Flask API which returns a JSON object, which is received by React and I render the predict route. This is handled using the Forms.jsx component, which has the following code:
const Form = () => {
const [title, setTitle] = useState("");
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
const movie_submit = {title};
console.log(movie_submit);
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate("/predict"));
})
}
return (
<div className='form_container'>
<form className='form' onSubmit={handleSubmit}>
<input type='text' placeholder='Movie Name' autoFocus
autoComplete='off' value={title} onChange={(e)=>setTitle(e.target.value)}/>
<button className='button'>Recommend!</button>
</form>
</div>
)
}
export default Form
Now I want to perform a GET request to the Flask API to get what should be put into the Predict.js page (/predict route), and the show it.
Predict.js is as:
const Predict = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetch('/predict').then(response =>
response.json().then(data =>
{
setMovies(Object.values(data));
}))
}, []);
const movie_name = movies.map((movie) => <p key={movie.toString()}>{movie}</p>);
return (
<div>
<Navbar />
<h1>Predictions</h1>
<br />
<h2><Movies movie={movie_name}/></h2>
</div>
)
}
export default Predict
But I want this to be such that if the user hasn't submitted the form, then it navigates to /apology route, and if the FLASK API GET request returns an empty object, even then it navigates to /apology route. How do I do this? I understand this is conditional routing of some sort, but I havent been able to quite achieve where I should do this. Here <Movies /> is simply a component that helps in showing the movie names
You can pass a data to the state prop of the location object.
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate('/predict', { state: { wasFetched: true } }));
})
then in your Predict Component:
const { state } = useLocation();
const { wasFetched } = state;
useEffect(() => {
if (wasFetched) {
// user submited the form
} else {
// user hasn't submited the form
}
}, [wasFetched]);

Why the flag is showed every time I reload the page after client has been created?

What I have done by far is when a user creates a client, in the top right of the page, is shown a flag(notification), which says "Client has been successfully created".
To do that was a little complex for me, because saving the client to DB, and listing the client to the web page are in two different components. Also, the flag is another component as well.
To save and list the clients I have used Axios since I'm dealing with the backend a lot.
SaveClient.js
export default function SaveClient({}) {
const save = async () => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
await axios
.post(
process.env.REACT_API_CLIENT, clientParams
)
.then((response) => {
navigate("/clientlist", {state: {showFlagCreate: true}}); //passing the state
})
.catch((error) => {;
console.log(error);
});
};
}
ClientList.js
export default function ClientList() {
const { state } = useLocation();
const showFlagCreate = state?.showFlagCreate;
const [clientlist, setClientList] = useState([])
useEffect(() => {
const clientParams = {
userName:
currentClient: clientName,
clientId: clientId,
};
axios
.get(process.env.REACT_API_CLIENT, clientParams)
.then((response) => {
const {data} = response
setClientList(data)
})
.catch((error) => console.log(error));
}, []);
return (
<div>
...
{showFlagCreate && <FlagCreateClient />}
</div>
);
}
FlagCreateClient
export default function FlagCreateClient() {
const [show, setShow] = useState(true);
return (
<div>
<Transition
show={show}
as={Fragment}
<div>
<p>The client is successfully created.</p>
</div>
<div>
<button onClick={() => {setShow(false)}}>
<span>Close</span>
</button>
</div>
</Transition>
<div/>
);
}
The idea is that in the SaveClient component, when a client is saved, in .then() inside the navigate() function, pass a state in a true condition.
Then in the ClinetList component, I call the state using useLocation(), and I passed in the component {showFlagCreate && <FlagCreateClient />}.
By far this logic works, I create a client, the flag is shown after, but when I reload the page after, the flag is shown. When I make other actions in the webpage which might be clicking the edit button and going back to the ClientList component the flag won't show, even if I reload/refresh the page.
How can I fix this bug?

Reloading the page loses API data using useContext

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 ;-)

Replacing POST Form in WOPI by axios code in server-side

I am trying to implement wopi protocol.
Using react, I have an iframe and a post-form which sets the content of iframe identified as collabora-online-viewer by requesting to address props.url with token props.token, as below (which works correctly):
useEffect(() => {
formElem.current.submit();
});
return (
<div style={{display: 'none'}}>
<form
ref={formElem}
action={props.url}
encType="multipart/form-data"
method="post"
target="collabora-online-viewer"
id="collabora-submit-form"
>
<input
name="access_token"
value={props.token}
type="hidden"
id="access-token"
/>
<input type="submit" value="" />
</form>
</div>
);
// my iframe in another component
<iframe
ref={iframeRef}
title="Collabora Online Viewer"
id="collabora-online-viewer"
name="collabora-online-viewer"
/>
Instead of sending this request by submitting a post-form, I need this request sent by my own express server using axios. How can i do this?
I have done as below, but it did not work:
//CLIENT SIDE
useEffect(() => {
sendRequest();
}, []);
async function sendRequest() {
const obj = {
url: props.url,
token: props.token
};
await axios
.post('/MyRequest', obj)
.then((res) => {
props.setIframeContent(res.data);
})
.catch((error) => {
console.log(error);
});
}
// my iframe in another component
const [iframeContent, setIframeContent] = useState('');
<iframe
srcDoc= {iframeContent}
ref={iframeRef}
title="Collabora Online Viewer"
id="collabora-online-viewer"
name="collabora-online-viewer"
/>
//SERVER SIDE
routeAuthenticated(
'POST',
'/MyRequest',
async (req, res) => {
try {
await axios
.post(req.body.url, req.body.token, {
// headers: {'Content-Type': 'multipart/form-data'}
})
.then((response) => {
res.send(response.data);
});
} catch (err) {
console.log(err);
}
}
);
Unfortunately it's not possible to post to Office Online Server with JavaScript. When an html form is submitted directly e.g.
<form action="https://someotherorigin.com">
Then no JavaScript is used, so CORS does not come into play when you post to OOS. But axios (or fetch or superagent etc.) use JavaScript, so CORS will block the request. See this SO post for more detail.

Next.js, make POST request on button click (SSG)

I'm doing an ssg website, it doesn't have backend, but it does start to have some features that fetch some data from another server. I checked the SWR and it works, the issue is I need to make a post request on button click, and it gets me an error:
hooks can only be called inside of the body of a function component
What I see is that I can create a function component, set up a call in it and just mount this component on button click, it works, but I'm having doubts about this approach.
This is probably done to work with get request, but I make a post.
ExportComponent renders on a page or in another component.
function ExportComponent() {
const [exportCalled, setExportCalled] = useState(false)
const exportCall = () => {
setExportCalled(true)
}
if (exportCalled) {
return (
<CallExport></CallExport>
)
}
return (
<Button
onClick={ exportCall() }
>
Export
</Button>
);
}
function CallExport() {
// api post call
const { data, isLoading } = exportProject();
if (isLoading) {
return <CircularProgress />;
}
return (
// call to api is done, make something with data
<Button>Open</Button>
)
}
export function exportProject() {
const params = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
};
const exportFetcher = (url) => fetch(url, params).then((r) => r.json());
const { data, error } = useSWR('url', exportFetcher);
return {
data,
isLoading: !error && !data,
isError: error
}
}
Is it wrong? Is there a better way?

Resources