Does order of state variable matters in React? - javascript

When I execute the code below gives an error "Cannot read properties of null (reading 'login')", because it reaches the return statement at the end, which it should not as I already have checks for the boolean before return.
import React, { useState, useEffect } from 'react';
const url = 'https://api.github.com/users/QuincyLarsn';
const MultipleReturns = () => {
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
fetch(url)
.then(data => {
if (data.status >= 200 && data.status <= 299)
return data.json();
else {
console.log("here");
setIsLoading(false);
setIsError(true);
console.log("here 2");
}
})
.then(result => {
setIsLoading(false);
setUser(result);
})
.catch(error => console.log(error))
}, []);
console.log(isError);
if (isLoading)
return <h2>Loading...</h2>
if (isError) {
return <h2>Error...</h2>
}
return <h2>{user.login}</h2>
};
export default MultipleReturns;
In the above code if setIsError(true) is placed before setIsLoading(false) in useEffect, then everything works fine but not vice versa, similarly if the url is correct then too things work fine if setUser(result) is placed before setIsLoading(false) and not vice versa.
I am not able to figure out why that is the case.

React is not batching state updates from fetch(). It is batched in case of event listeners. This is an async fetch call.
In this sandbox console, you can see that there is a render in between your state updates - setIsLoading(false) and setIsError(true).
So for one render cycle : isLoading is false and isError is also false. That will lead to the error condition.
You can use unstable_batchedUpdates to enforce batching.
import { useEffect, useState } from "react";
import { unstable_batchedUpdates } from "react-dom";
import "./styles.css";
const url = "https://api.github.com/users/QuincyLarsn";
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
fetch(url)
.then((data) => {
if (data.status >= 200 && data.status <= 299) return data.json();
else {
console.log("here");
unstable_batchedUpdates(() => {
setIsLoading(false);
setIsError(true);
});
console.log("here 2");
}
})
.then((result) => {
setIsLoading(false);
setUser(result);
})
.catch((error) => {
console.log(error);
});
}, []);
console.log("isError", isError);
if (isLoading) return <h2>Loading...</h2>;
if (isError) return <h2>Error...</h2>;
return <h2>{user.login}</h2>;
};
export default App;
Corrected Sandbox Link

In a such case, order does matter.
While React may batch updates in this case, it's not guaranteed and even if it does, it may call the render function with the in-between state.
So, when isLoading is set to false, but user is not yet set, you get an error.
You can fix this by setting the user first, and then making isLoading false.
But the real solution would be to eliminate the unnecessary state variables: isLoading is true while isError is false and user is null, and false otherwise.
So, you can do it like this:
should not as I already have checks for the boolean before return.
import React, { useState, useEffect } from 'react';
const url = 'https://api.github.com/users/QuincyLarsn';
const MultipleReturns = () => {
const [isError, setIsError] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
fetch(url)
.then(data => {
if (data.status >= 200 && data.status <= 299)
return data.json();
else {
console.log("here");
setIsError(true);
console.log("here 2");
}
})
.then(result => {
setUser(result);
})
.catch(error => console.log(error))
}, []);
console.log(isError);
if (isError) {
return <h2>Error...</h2>
}
if (user !== null) {
return <h2>{user.login}</h2>
}
return <h2>Loading...</h2>
};
export default MultipleReturns;

After you set isLoading as false, the code moves to the last return statement as the error is still false at the moment. So first setting the error blocks the code at the second return statement.
Similarly if you set isLoading as false then set the user, the code will move to the last return statement before the user is set and it will show error. Setting the user and then making isLoading as false shows the user perfectly.

Related

Set loading with React useEffect fails

I'm trying to show a loading spinner while fetching data from an api, and show error or results once finished using useEffect. For some reason isLoading is set to false before fetching is finished.
Could you please help me what could be the problem?
const ProblemTable: React.FC<IProblemTableProps> = (props) => {
// List of problems to show on the table
const [problems, setProblems] = useState<IProblem[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<Error>();
useEffect(() => {
TierService.getProblems(props.tier, setProblems, setError);
}, []);
useEffect(() => {
setIsLoading(false);
}, [problems, error]);
if (isLoading) {
return <Loading message="Loading problems..." />;
}
if (error) {
return <ErrorMessage title="Failed fetching problems" />;
}
return <>...</>;
The useEffect that sets loading to false is called when the component mounts. Add a condition that would only turn loading to false when the api call ended.
For example - if there are any problems or there's an error set loading to false:
useEffect(() => {
if(problems.length || error) setIsLoading(false);
}, [problems, error]);
However, if there are no problems or an error, loading would stay true, so you'll probably need a more strict condition. A better way would be to set loading to false if any of the callbacks are called:
useEffect(() => {
TierService.getProblems(
props.tier,
p => {
setProblems(p);
setLoading(false);
}
e => {
setError(e);
setLoading(false);
}
);
}, []);
Depending on the implementation of TierService.getProblems the 2nd suggestion would fail if no callback is called when the response is empty.
I would probably create a Promise based API which would allow you to use async/await with try/catch/finally:
useEffect(() => {
const api = async () => {
try {
const problems = await TierService.getProblems(props.tier);
setProblems(problems);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
};
api();
}, []);

React simple useEffect infinit loop

I do not understand why this code is an infinit loop. I this it is because I update my state in else if condition. But i'd like to update an array of urls when getting new api answer.
import React, { useState, useEffect } from 'react';
function getShortenUrl() {
const [urls, setUrls] = useState([])
const [error, setError] = useState(null);
const [items, setItems] = useState({});
useEffect(() => {
fetch("https://api.shrtco.de/v2/shorten?url=https://stackoverflow.com/questions/37435334/correct-way-to-push-into-state-array")
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
}, [])
if (error) {
return <div>Error : {error.message}</div>;
} else if (!items.result) {
return <div>Loading...</div>;
console.log("no item");
} else {
setUrls([urls, items]);
console.log("items", items);
console.log("urls", urls);
return <ul> {items.result.short_link}</ul>;
}
}
export default getShortenUrl;
I am kindof lost when it comes to state actually. I do not understand how I can create an array of urls and be able to use it in other components.
You may have some mistakes here
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
Change it into
.then((result) => {
setItems(result);
setUrls([...urls, result])
})
.catch((error) => {
setError(error);
})
And also remove the line setUrls([urls, items]);
If you are new to react then we make Loading state for API Call fetch and you are setting state in else and when state updates component re-renders so on every component re-render you are updating state which is causing infinite loop. Try this
import React, { useState, useEffect } from 'react';
function getShortenUrl() {
const [urls, setUrls] = useState([])
const [error, setError] = useState(null);
// const [items, setItems] = useState({}); // remove it because you are using urls and items state for same purposes
const [loading,setLoading]=useState(false);
useEffect(() => {
setLoading(true);
fetch("https://api.shrtco.de/v2/shorten?url=https://stackoverflow.com/questions/37435334/correct-way-to-push-into-state-array")
.then(res => res.json())
.then(
(result) => {
// setItems(result); // remove it
setUrls(result);
setLoading(false);
},
(error) => {
setError(error);
setLoading(false);
}
)
}, [])
if(loading){
return <div>Loading...</div>;
}
if (error) {
return <div>Error : {error.message}</div>;
} else {
return <ul> {urls?.result?.short_link}</ul>;
}
}
export default getShortenUrl;
Try to use a conditional rendering instead of if else statement:
import React, { useState, useEffect } from 'react';
function getShortenUrl() {
const [urls, setUrls] = useState([])
const [error, setError] = useState(null);
const [items, setItems] = useState({});
useEffect(() => {
fetch("https://api.shrtco.de/v2/shorten?url=https://stackoverflow.com/questions/37435334/correct-way-to-push-into-state-array")
.then(res => res.json())
.then(
(result) => {
setItems(result);
},
(error) => {
setError(error);
}
)
}, [])
return (
<>
{error&& <div>Error : {error.message}</div>}
{!items.result && <div>Loading...</div> }
{items.result && <ul> {items.result.short_link}</ul>}
</>
)
}
export default getShortenUrl;

How to connect flask rest API output to react and display result

I am doing inference using flask rest API and I got the result
{
result: {
predictions: -3.4333341121673584
} }
bypassing multiple args in the get as the URL I got the above result
http://127.0.0.1:3000/predict?solute=CC(C)(C)Br&solvent=CC(C)(C)O Now I want to use this result to use in a react app.
I have written the code below
import { useState, useEffect } from "react";
function App() {
const [state, setState] = useState({});
useEffect(() => {
fetch("api/")
.then((response) => {
if (response.status == 200) {
return response.json();
}
})
.then((data) => console.log(data))
.then((error) => console.log(error));
});
I have written the following using a tutorial on the internet. I am new to using fetch API or Axios. Need help to get this result in react app
import { useState, useEffect } from "react";
function App() {
const [data, setData] = useState(null);
const [loading, setLoading = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("http://127.0.0.1:3000/predict?solute=CC(C)(C)Br&solvent=CC(C)(C)O")
.then((response) => {
if (response.status == 200) {
return response.json();
}
})
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) {
return <p>Loading</p>
}
if (error) {
return <p>{JSON.stringify(error)}</p>
}
return <p>{JSON.stringify(data)}</p>
}

Multiple useEffect not working as expected

useEffect(() => {
debugger;
}, [filter]);
// eslint-disable-next-line
useEffect(async () => {
if (parseInt(localStorage.getItem("lastFetchTime")) + 8640000 > Date.now()) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
await fetch('https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2')
.then((resp) => resp.json())
.then((data) => {
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
})
}
}, []);
I have these 2 useEffect in my program, the first one, with the listener is not being called even if the filter is changed. But it works if I remove the [] from the 2nd useEffect and the 2nd one runs on loop so I cant use it like that. I saw multiple forums, all of which suggests this should work.
import { useState, useEffect } from "react";
import { render } from "react-dom";
const sleep = (ms: number) => new Promise(
resolve => setTimeout(() => resolve('Resolved'), ms));
function App() {
const [filter, setFilter] = useState({ count: 0 });
const [get, set] = useState(0);
useEffect(() => {
console.log('Here');
}, [filter]);
useEffect(() => {
async function myFunction() {
const res = await sleep(5000)
.then(res => console.log(res));
setFilter({ ...filter, count: filter.count + 1 });
}
myFunction();
}, [get]);
return (
<div>
<p>App {get}</p>
<button onClick={() => set((get: number) => get + 1)}>
Click
</button>
</div>
);
}
render(<App />, document.getElementById("root"));
This minor snippet to be working for me as expected.
useEffect cannot be async. If you want to call an async function in useEffect() you need to do it like this:
EDIT: this is the complete useEffect
useEffect(() => {
async function getData() {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const res = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
);
const data = await res.json();
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}
getData();
}, []);
I tested it and it worked as expected (I console.log() in the other useEffect())
There's nothing wrong with the useEffect. It's a bullet proof. But you make sure the following things:
Is filter updated during the component did mount?
The debugger will show up if you have open developer tool.
Isfilter updated during the component did update?
The debugger won't show up.
To make sure whenfilter is updated, use another effect hook but this time without dependency array.
useEffect(()=>{
console.log(filter) // analyze in the console
})
And if the value is updated during the update then you don't need to use dependency array but check the changes inside the effect hook by using some state for that as filter is coming from the update (props).
import { useState, useEffect, useCallback } from "react";
function App() {
const [isLoading, setIsLoading] = useState(false);
const [filter, setRecipeList] = useState({});
useEffect(() => {
// debugger;
}, [filter]);
// eslint-disable-next-line
const fetchData = useCallback(async () => {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const data = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
).then((resp) => resp.json());
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}, []);
useEffect(() => {
setIsLoading(true);
fetchData();
}, [fetchData]);
return (
<div>
<span>{isLoading ? "loading" : "loaded!"}</span>
{!isLoading && filter && <div>filter size:{filter.length}</div>}
</div>
);
}
export default App;
I think it will work properly.
Thanks.

Cleaning component states useEffect

I have states :
const { id } = useParams<IRouterParams>();
const [posts, setPosts] = useState<IPost[]>([]);
const [perPage, setPerPage] = useState(5);
const [fetchError, setFetchError] = useState("");
const [lastPostDate, setLastPostDate] = useState<string | null>(null);
// is any more posts in database
const [hasMore, setHasMore] = useState(true);
and useEffect :
// getting posts from server with first render
useEffect(() => {
console.log(posts);
fetchPosts();
console.log(hasMore, lastPostDate);
return () => {
setHasMore(true);
setLastPostDate(null);
setPosts([]);
mounted = false;
return;
};
}, [id]);
When component change (by id), I would like to clean/reset all states.
My problem is that all states are still the same, this setState functions in useEffect cleaning function doesn't work.
##UPDATE
// getting posts from server
const fetchPosts = () => {
let url;
if (lastPostDate)
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}&date=${lastPostDate}`;
else
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}`;
api
.get(url, {
headers: authenticationHeader(),
})
.then((resp) => {
if (mounted) {
if (resp.data.length === 0) {
setFetchError("");
setHasMore(false);
setPosts(resp.data);
return;
}
setPosts((prevState) => [...prevState, ...resp.data]);
if (resp.data.length < perPage) setHasMore(false);
setLastPostDate(resp.data[resp.data.length - 1].created_at);
setFetchError("");
}
})
.catch((err) => setFetchError("Problem z pobraniem postów."));
};
if your component isnt unmounted, then the return function inside useEffect will not be called.
if only the "id" changes, then try doing this instead:
useEffect(() => {
// ... other stuff
setHasMore(true);
setLastPostDate(null);
setPosts([]);
return () => { //...code to run on unmount }
},[id]);
whenever id changes, the codes inside useEffect will run. thus clearing out your states.
OK, I fixed it, don't know if it is the best solution, but works...
useEffect(() => {
setPosts([]);
setHasMore(true);
setLastPostDate(null);
return () => {
mounted = false;
return;
};
}, [id]);
// getting posts from server with first render
useEffect(() => {
console.log(lastPostDate, hasMore);
hasMore && !lastPostDate && fetchPosts();
}, [lastPostDate, hasMore]);

Categories

Resources