Right way to use REST API in react? - javascript

I am using a free account on weatherstack.com to get weather info on a specified city. The relevant component from the App is the following
const WeatherInfo = (props) => {
const [weather, setWeather] = useState()
const url = 'http://api.weatherstack.com/current?access_key='
+ process.env.REACT_APP_API_KEY + '&query=' + props.city
axios.get(url)
.then(response => {setWeather(response.data)})
return (
<div>
<p>Weather in {props.city}</p>
<p><b>Temperature:</b> {weather.current.temperature} celsius</p>
<img src={weather.current.weather_icons[0]} alt="Country flag" width="150"></img>
<p><b>Wind:</b> {weather.current.wind_speed} mph direction {weather.current.wind_dir}</p>
</div>
)
}
This fails with TypeError: weather.current is undefined because I believe the axios.get is asyncronously called so the return happens before the setWeather() call inside the .then(). So I replaced the return statement with the following:
if (weather === undefined) {
return (
<div>Loading...</div>
)
}
else {
return (
<div>
<p>Weather in {props.city}</p>
<p><b>Temperature:</b> {weather.current.temperature} celsius</p>
<img src={weather.current.weather_icons[0]} alt="Country flag" width="150"></img>
<p><b>Wind:</b> {weather.current.wind_speed} mph direction {weather.current.wind_dir}</p>
</div>
)
}
This succeeds briefly and then fails with the same error as previous. I guess I must have some fundamental misunderstanding of the correct way to wait for a response before rendering.
Question
What is the correct way to wait for REST call when using react?

Whenever you set state, your functional component will re-render, causing the body of the function to execute again. This means that when your axios call does eventually complete, doing setWeather(response.data) will cause your functional component body to run again, making you do another get request.
Since you only want to run your axios get request once, you can put your axios get request inside of the useEffect() hook. This will allow you to only run your get request when the component initially mounts:
import React, {useState, useEffect} from "react";
...
const WeatherInfo = (props) => {
const [weather, setWeather] = useState();
useEffect(() => {
const url = 'http://api.weatherstack.com/current?access_key=' + process.env.REACT_APP_API_KEY + '&query=' + props.city
const cancelTokenSource = axios.CancelToken.source();
axios.get(url, {cancelToken: cancelTokenSource.token})
.then(response => {setWeather(response.data)});
return () => cancelTokenSource.cancel();
}, [props.city, setWeather]);
return weather ? (
<div>
<p>Weather in {props.city}</p>
<p><b>Temperature:</b> {weather.current.temperature} celsius</p>
<img src={weather.current.weather_icons[0]} alt="Country flag" width="150"></img>
<p><b>Wind:</b> {weather.current.wind_speed} mph direction {weather.current.wind_dir}</p>
</div>
) : <div>Loading...</div>;
}
The above useEffect() callback will only run when the things in the dependency array (second argument of useEffect) change. Since setWeather is a function and doesn't change, and props.city only changed when the prop is changed, the callback is only executed when your component initially mounts (provided the city prop isn't changing). The useEffect() hook also allows you to return a "clean-up" function, which, in this case, will get called when your component unmounts. Here I have used Axios's CancelToken to generate a source token so that you can cancel any outgoing requests if your component unmounts during the request.

First of all wrap axios in useEffect:
useEffect(() => {
axios.get(url)
.then(response => {setWeather(response.data)})
});
then wrap return part with weather in condition:
return (
<div>
<p>Weather in {props.city}</p>
{
weather && (
<>
<p><b>Temperature:</b> {weather.current.temperature} celsius</p>
<img src={weather.current.weather_icons[0]} alt="Country flag" width="150"></img>
<p><b>Wind:</b> {weather.current.wind_speed} mph direction {weather.current.wind_dir}</p>
</>
)
}
</div>
)

Your code will continuously make the axios call on every rerender. You need to put that call into a function and call if once on Component load. Also your render function should check to see if the state is set. Here is a basic example and a Sandbox:
https://codesandbox.io/s/zen-glitter-1ci2j?file=/src/App.js
import React from "react";
import "./styles.css";
const axioscall = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ city: "Scottsdale", tempt: 75 });
}, 1000);
});
};
export default (props) => {
const [weather, setWeather] = React.useState(null);
React.useEffect(() => {
axioscall()
.then((result) => setWeather(result));
}, []);
return (
<div>
{weather ? (
<>
<p>Weather in {weather.city}</p>
<p>
<b>Temperature:</b> {weather.temp} F
</p>
</>
) : (
<div>Loading...</div>
)}
</div>
);
};

Related

How to update my useEffect hook when State change happen in a different .js file's component in ReactJS?

I am trying to make an API call in useEffect() and want useEffect() to be called everytime a new data is added in the backend.
I made a custom Button(AddUserButton.js) which adds a new user in backend. I am importing this button in the file (ManageUsers.js) where I am trying to display all the users. I just wanted to make an useState to keep track everytime an add button is clicked and make useEffect refresh according to it. For Example:
const [counter, setCounter] = useState(0);
...
const handleAdd = () => {
setCounter(state => (state+1));
};
...
useEffect(() => {
// fetch data here
...
}, [counter]);
...
return(
<Button onClick = {handleAdd}> Add User </Button>
);
But currently because I have two .js files, I am not sure how to make my logic stated above
work in this case
ManageUsers.js
import AddUserButton from "./AddUserButton";
...
export default function ManageShades() {
...
useEffect(() => {
axios
.get("/api/v1/users")
.then(function (response) {
// After a successful add, store the returned devices
setUsers(response.data);
setGetUserFailed(false);
})
.catch(function (error) {
// After a failed add
console.log(error);
setGetUserFailed(true);
});
console.log("Load User useeffect call")
},[]);
return (
<div>
...
<Grid item xs={1}>
<AddUserButton title = "Add User" />
</Grid>
...
</div>
);
}
AddUserButton.js
export default function AddDeviceButton() {
...
return (
<div>
<Button variant="contained" onClick={handleClickOpen}>
Add a device
</Button>
...
</div>
);
}
A common approach is to pass a callback function to your button component that updates the state of the parent component.
import { useState, useEffect } from "react";
const AddUserButton = ({ onClick }) => {
return <button onClick={onClick} />;
};
export default function Test() {
const [updateCount, setUpdateCount] = useState(false);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count++);
}, [updateCount]);
return (
<div>
<AddUserButton
onClick={() => {
// do something, e.g. send data to your API
// finally, trigger update
setUpdateCount(!updateCount);
}}
/>
</div>
);
}
So it seems like you are trying to let a child update it's parent's state, an easy way to do this is to let the parent provide the child a callback, which will update the parent's state when called.
const parent = ()=>{
const [count, setCount] = useState(0);
const increCallback = ()=>{setCount(count + 1)};
return (<div>
<child callback={increCallback}/>
</div>);
}
const child = (callback)=>{
return (<button onClick={callback}/>);
}
If you were to tell the ManageUsers component to fetch from the back-end right after the AddUser event is fired, you will almost certainly not see the latest user in the response.
Why? It will take some time for the new user request to be received by the back-end, a little longer for proper security rules to be passed, a little longer for it to be formatted, sanitized, and placed in the DB, and a little longer for that update to be available for the API to pull from.
What can we do? If you manage the users in state - which it looks like you do, based on the setUsers(response.data) - then you can add the new user directly to the state variable, which will then have the user appear immediately in the UI. Then the new user data is asynchronously added to the back-end in the background.
How can we do it? It's a really simple flow that looks something like this (based roughly on the component structure you have right now)
function ManageUsers() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://api.com')
.then(res => res.json())
.then(res => setUsers(res));
.catch(err => console.error(err));
}, [setUsers]);
const handleAdd = ({ name, phone, dob }) => {
const newUser = {
name,
phone,
dob
};
setUsers([...users, newUser]);
};
return (
<div>
<UserList data={users} />
<AddUser add={handleAdd} />
</div>
);
}
// ...
function AddUser({ add }) {
const [userForm, setUserForm] = useState({ name: "", phone: "", dob: "" });
return (
// controlled form fields
<button onClick={() => add(userForm)}>Submit</button>
);
}
// ...
function UserList({ data }) {
return (
<>
{data.map(user =>
<p>{user.name></p>
}
</>
);
}
Once the user adds a new user with the "Submit" button, it passes the new user to the "add" function which has been passed down as a prop. Then the user is appended to the users array of the ManageUsers component, instantly populating the latest user data in the UserList component. If we wait for a new fetch request to come through, this will add a delay, and the newest user we just added will not likely come back with the response.

How to instantly display data after an API call in react hooks

I don't understand why the second line, which reads data from the props, is not displayed as instantly as the first, i would like them to be displayed instantly
I update the state when a button is clicked, which calls api, data is coming in, the state is updating, but the second line requires an additional press to display
How to display both lines at once after a call? What's my mistake?
I'm using react hooks, and i know that required to use useEffect for re-render component, i know, that how do work asynchronous call,but i'm a little confused, how can i solve my problem, maybe i need to use 'useDeep effect' so that watching my object properties, or i don't understand at all how to use 'useEffect' in my situation, or even my api call incorrectly?
I have tried many different solution methods, for instance using Promise.all, waiting for a response and only then update the state
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./test";
ReactDOM.render(<App />, document.getElementById("root"));
app.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const useDataApi = (initialState) => {
const [state, setState] = useState(initialState);
const stateCopy = [...state];
const setDate = (number, value) => {
setState(() => {
stateCopy[number].date = value;
return stateCopy;
});
};
const setInfo = async () => {
stateCopy.map((item, index) =>
getFetch(item.steamId).then((res) => setDate(index, res.Date))
);
};
const getFetch = async (id) => {
if (id === "") return;
const requestID = await fetch(`https://api.covid19api.com/summary`);
const responseJSON = await requestID.json();
console.log(responseJSON);
const result = await responseJSON;
return result;
};
return { state, setState, setInfo };
};
const Children = ({ data }) => {
return (
<>
<ul>
{data.map((item) => (
<li key={item.id}>
{item.date ? item.date : "Not data"}
<br></br>
</li>
))}
</ul>
</>
);
};
const InfoUsers = ({ number, steamid, change }) => {
return (
<>
<input
value={steamid}
numb={number}
onChange={(e) => change(number, e.target.value)}
/>
</>
);
};
function App() {
const usersProfiles = [
{ date: "", id: 1 },
{ date: "", id: 2 }
];
const profiles = useDataApi(usersProfiles);
return (
<div>
<InfoUsers number={0} change={profiles.setID} />
<InfoUsers number={1} change={profiles.setID} />
<button onClick={() => profiles.setInfo()}>Get</button>
<Children data={profiles.state} loading={profiles} />
</div>
);
}
export default App;
To get the data, just click GET
In this example, completely removed useEffect, maybe i don’t understand how to use it correctly.
P.s: Sorry for bad english
You don't need stateCopy, as you have it in the callback of the setState:
const setInfo = async () => {
// we want to update the component only once
const results = await Promise.all(
state.map(item => getFetch(item.steamId))
);
// 's' is the current state
setState(s =>
results.map((res, index) => ({ ...s[index], date: res.Date })
);
};

how to make API request only once for each page in REACT INFINITE scroll here

I am working on one React infinite scroll component and its working fine and perfectly, only one small issue is that for page number 1, it makes twice the API request, which I do not want and for other pages ex 2,3,4 it makes request only once.
I tried everything but i am unable to modify the code so that for page number 1,it makes only once the request.
How can i make only once the request for page number 1 also ?
here is the working code.
import React, { useState, useEffect } from "react";
import axios from "axios";
function Lists() {
const [posts, setPosts] = useState([]);
const [newposts, setNewposts] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const [page, setPage] = useState(1);
const LIMIT = 7;
const getPosts = () => {
axios.get(`https://jsonplaceholder.typicode.com/posts?_limit=${LIMIT}&_page=${page}`)
.then(res => {
setNewposts(res.data);
setPosts([...posts, ...res.data]);
setIsFetching(false);
})
};
const getMorePosts= () => {
setPage(page + 1);
getPosts();
}
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop !==
document.documentElement.offsetHeight
) return;
setIsFetching(true);
}
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
getPosts();
},[]);
useEffect(() => {
if (!isFetching){
return;
}
if( newposts.length > 0 ){
getMorePosts();
}
}, [isFetching]);
return (
<div className="App">
{posts.map((post, index) => (
<div key={index} className="post">
<div className="number">{post.id}</div>
<div className="post-info">
<h2 className="post-title">{post.title}</h2>
<p className="post-body">{post.body}</p>
</div>
</div>
))}
{isFetching && newposts.length > 0 && (
<div style = {{display: "flex", justifyContent:"center"}}>
<div className="spinner-border" role="status">
<span className="sr-only">Loading...</span>
</div>
</div>
)}
</div>
);
}
export default Lists;
The issue is that setPage, like any state setter, works asynchronously - it doesn't update the value of page immediately like it appears you might be expecting it to. The value of page will only be updated in a subsequent render which is triggered by the state change.
That means that in getMorePosts() you're not really getting page + 1, you're just getting page.
That means on first render you're calling getPosts() in that other useEffect, with page set to 1, then calling it again when you scroll and call getPosts() inside the getMorePosts() call.
Subsequent calls only happen once because getMorePosts() increments page in every subsequent render.
As for a fix, a quick one might be to just take page as an arg to getPosts, then you can statically call getPosts(1) on first render and keep the rest of the logic the same but init the page state to 2 and change the call to getPage(page) inside getMorePosts().

Refresh specific component in React

I'm using functional component in React and i'm trying to reload the component after button is clicked.
import { useCallback, useState } from 'react';
const ProfileLayout = () => {
const [reload, setReload] = useState(false);
const onButtonClick = useCallback(() => {
setReload(true);
}, []);
return (
{reload && (
<ProfileDetails />
)}
<Button onClick={onButtonClick} />
);
};
export default ProfileLayout;
I'm not able to see again the component after page loaded.
Note: I don't want to use window.location.reload();
Note: I don't want to use window.location.reload();
That is good because that is not the correct way to do this in React. Forcing the browser to reload will lose all of your component or global state.
The correct way to force a component to render is to call this.setState() or call this.forceUpdate().
If you need to force the refresh, then better use a number than a boolean.
const ProfileLayout = () => {
const [reload, setReload] = useState(0);
const onButtonClick = useCallback(() => {
setReload(p => p+1);
}, []);
return (
{Boolean(reload) && (
<ProfileDetails />
)}
);
};
What do you mean by reloading the component? You want to re-render it or you want to make the component fetch the data again? Like "refresh"?
Anyways the way your component is coded the <ProfileDetails /> component will not show up on the first render since you are doing reload && <ProfileDetails />, but reload is initially false. When you click the button then ProfileDetails will appear, but another click on the button won't have any effect since reload is already set to true.
If you want to refresh the data the component uses, then you need to implement a callback that triggers the data fetching.
Edit after clarification by author
const ProfileContainer = (props) => {
// initialProfile is the profile data that you need for your component. If it came from another component, then you can set it when the state is first initialized.
const [ profile, setProfile ] = useState(props.initialProfile);
const loadProfile = useCallback( async () => {
// fetch data from server
const p = await fetch('yourapi.com/profile'); // example
setProfile(p);
}
return (<><ProfileDetails profile={profile} /> <button onClick={loadProfile} /></>)
}
Alternate approach to load the data within the component
const ProfileContainer = (props) => {
const [ profile, setProfile ] = useState(null);
const loadProfile = useCallback( async () => {
// fetch data from server
const p = await fetch('yourapi.com/profile'); // example
setProfile(p);
}
useEffect(() => loadProfile(), []); // Empty dependency so the effect only runs once when component loads.
return (<>
{ /* needed since profile is initially null */
profile && <ProfileDetails profile={profile} />
}
<button onClick={loadProfile} />
</>);
};

How to get data from server and use a hook to retrieve it in multiple components?

I have a rather basic use-case: I want to get the user info from the server when the app loads and then using a hook to get the info in different components.
For some reason, I run into an infinite loop and get Error: Maximum update depth exceeded.
getMe gets called recursively until the app crashes.
Is that a correct hook behavior?
This is the relevant part of the hook:
export default function useUser () {
const [user, setUser] = useState(null)
const [authenticating, setAuthenticating] = useState(true)
// ...
const getMe = (jwt) => {
console.log('set user')
axios.get(baseURL + endpoints.currentUser, { headers: {
'X-Access-Token': jwt,
'Content-Type': 'application/json'
}}).then(response => {
setUser({
name: response.data.name,
img: response.data.avatar_url
})
})
}
useEffect(() => {
getMe(jwt)
}, [])
return { user, authenticating }
}
This is the first call
function App () {
const { user, authenticating } = useUser()
const c = useStyles()
return (
authenticating ? (
<div className={c.wrapper}>
<Loader size={60}/>
</div>
) : (
<div className={c.wrapper}>
<div className={c.sidebar}>
<img className={c.lamp} src={ user ? user.img : lamp } />
And I also call need the user in the Router component
const Routes = () => {
const { user } = useUser()
return (
<Router history={history}>
<Switch>
// ...
<Route
path={pages.login}
render={routeProps => (
user ?
<Redirect to={pages.main}/> :
<Login {...routeProps}/>
)}
/>
You shouldn't be requesting the server each time you call the hook since it pretty much unnecessary. You could use Redux or context for this (for this paradigm redux would be better). However, if you insist on this method, it seems you have to wrap your getMe function in a useCallback hook since it must be re-rendering each time the function runs.
Read more on the useCallback hook:
https://reactjs.org/docs/hooks-reference.html#usecallback
You're now making a request via the useEffect in your custom hook - why not let the component do that programatically?
Change getMe to a useCallback function and export it:
export default function useUser () {
const [user, setUser] = useState(null)
const [authenticating, setAuthenticating] = useState(true)
// ...
const getMe = useCallback((jwt) => {
console.log('set user')
axios.get(baseURL + endpoints.currentUser, { headers: {
'X-Access-Token': jwt,
'Content-Type': 'application/json'
}}).then(response => {
setUser({
name: response.data.name,
img: response.data.avatar_url
})
})
}, [])
return { user, authenticating, doFetch: getMe }
}
..and use that function in your components (import doFetch and call it on mount), e. g.:
function App () {
const { user, authenticating, doFetch } = useUser()
const c = useStyles()
useEffect(() => doFetch(), [doFetch])
return (
authenticating ? (
<div className={c.wrapper}>
<Loader size={60}/>
</div>
) : (
<div className={c.wrapper}>
<div className={c.sidebar}>
<img className={c.lamp} src={ user ? user.img : lamp } />
You now avoid the infinite loop and your component takes control of the request logic instead of the reusable hook.

Categories