Infinite Re-render even with the use of useEffect Hook - reactjs

I am trying to create an authorized user checker with React, however, my component keeps on re-rendering.
Here's my approach:
I created an authBreaker state so that my useEffect will update every time it changes and not the auth state which is being updated inside the useEffect.
However, it still keeps on re-rendering even though authBreaker is not updated.
I also, tried removing the dependencies of my useEffect using [] But it resulted to the same behavior.
const Login = React.memo(() => {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [authBreaker, setAuthBreaker] = useState(false)
const [auth, setAuth] = useState(false)
const isAuthorized = () => {
const user = Cookies.get('token')
user && setAuth(true)
console.log(auth)
}
const login = async () => {
try {
//server code here...
//if login successful..
setAuthBreaker(true)
} catch (e) {
console.log(e)
}
}
useEffect(() => {
isAuthorized()
}, [authBreaker])
return (
<div className="pml-form">
{ auth && <Redirect to="/"/> }
//code here...
</div>
)
})
Any idea why it keeps on re-rendering? Thanks!

Related

React JS: My view model is causing an endless loop of rendering

I am new to React JS, coming from iOS and so trying to implement MVVM. Not sure it's the right choice, but that's not my question. I would like to understand how react works and what am I doing wrong. So here's my code:
const ViewContractViewModel = () => {
console.log('creating view model');
const [value, setValue] = useState<string | null>(null);
const [isLoading, setisLoading] = useState(false);
async function componentDidMount() {
console.log('componentDidMount');
setisLoading(true);
// assume fetchValueFromServer works properly
setValue(await fetchValueFromServer());
console.log('value fetched from server');
setisLoading(false);
}
return { componentDidMount, value, isLoading };
};
export default function ViewContract() {
const { componentDidMount, value, isLoading } = ViewContractViewModel();
useEffect(() => {
componentDidMount();
}, [componentDidMount]);
return (
<div className='App-header'>
{isLoading ? 'Loading' : value ? value : 'View Contract'}
</div>
);
}
So here's what I understand happens here: the component is mounted, so I call componentDidMount on the view model, which invokes setIsLoading(true), which causes a re-render of the component, which leads to the view model to be re-initialised and we call componentDidMount and there's the loop.
How can I avoid this loop? What is the proper way of creating a view model? How can I have code executed once after the component was presented?
EDIT: to make my question more general, the way I implemented MVVM here means that any declaration of useState in the view model will trigger a loop every time we call the setXXX function, as the component will be re-rendered, the view model recreated and the useState re-declared.
Any example of how to do it right?
Thanks a lot!
A common pattern for in React is to use{NameOfController} and have it completely self-contained. This way, you don't have to manually call componentDidMount and, instead, you can just handle the common UI states of "loading", "error", and "success" within your view.
Using your example above, you can write a reusable controller hook like so:
import { useEffect, useState } from "react";
import api from "../api";
export default function useViewContractViewModel() {
const [data, setData] = useState("");
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
(async () => {
try {
const data = await api.fetchValueFromServer();
setData(data);
} catch (error: any) {
setError(error.toString());
} finally {
setLoading(false);
}
})();
}, []);
return { data, error, isLoading };
}
Then use it within your view component:
import useViewContractViewModel from "./hooks/useViewContractViewModel";
import "./styles.css";
export default function App() {
const { data, error, isLoading } = useViewContractViewModel();
return (
<div className="App">
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error}</p>
) : (
<p>{data || "View Contract"}</p>
)}
</div>
);
}
Here's a demo:
On a side note, if you want your controller hook to be more dynamic and can control the initial data set, then you can pass it props, which would then be added to the useEffect dependency array:
import { useEffect, useState } from "react";
import api from "../api";
export default function useViewContractViewModel(id?: number) {
const [data, setData] = useState("");
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
(async () => {
try {
const data = await api.fetchValueFromServer(id);
setData(data);
} catch (error: any) {
setError(error.toString());
} finally {
setLoading(false);
}
})();
}, [id]);
return { data, error, isLoading };
}
Or, you return a reusable callback function that allows you to refetch data within your view component:
import { useCallback, useEffect, useState } from "react";
import api from "../api";
export default function useViewContractViewModel() {
const [data, setData] = useState("");
const [isLoading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchDataById = useCallback(async (id?: number) => {
setLoading(true);
setData("");
setError("");
try {
const data = await api.fetchValueFromServer(id);
setData(data);
} catch (error: any) {
setError(error.toString());
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
fetchDataById()
}, [fetchDataById]);
return { data, error, fetchDataById, isLoading };
}

How to fix loading spinner when using useEffect Hooks with dependency list?

I am facing one problem when trying to add a spinner.
My problem is when I add "product" dependency in useEffect hooks then my loading spinner always spinning and data not showing.
Here is my code :
const [product, setProduct] = useState([]);
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState(false);
const navigate = useNavigate();
// show all products on the manage inventory page
useEffect(() => {
setLoading(true);
(async () => {
const data = await fetchAllProduct();
if (data) {
setProduct(data);
setLoading(false);
}
})();
}, [product]);
Bellow the return code >>
{
loading === false ? <ProductTable
viewProductHandle={viewProductHandle}
deleteProductHandle={deleteProductHandle}
product={product}>
</ProductTable> : <Spinner></Spinner>
}
So how do I fix that? pls, help me...
It's happening because you're only setting your loading state to false when data is a truthy. Moreover, your loading state can start with a true value, and you set it to false inside your useEffect after finishing the request. This way you're going to avoid initializing the state with a false value, setting it to true, and then back to false.
I also think your product state doesn't need to be on dependency array, because you're only setting it once, after the fetchAllProduct() call, so it'll probably not change again, as you're not passing setProducts down to your ProductTable component.
So your code should be something like this:
const [product, setProduct] = useState([]);
const [msg, setMsg] = useState('');
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
// show all products on the manage inventory page
useEffect(() => {
(async () => {
const data = await fetchAllProduct();
if (data) {
setProduct(data);
}
setLoading(false);
})();
}, []);

How to fix this error when I use useffect on react: React Hook useEffect has a missing dependency: 'props.history'

I can't really figure whats wrong with my code and I get this error:
" React Hook useEffect has a missing dependency: 'props.history'. Either include it or remove the dependency array "
const mapState = ({ user }) => ({
signInSuccess: user.signInSuccess
});
const SignIn = props => {
const { signInSuccess } = useSelector(mapState);
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
useEffect(() => {
if (signInSuccess){
resetForm()
props.history.push('/')
}
}, [signInSuccess])
const resetForm = () => {
setEmail('');
setPassword('');
}
Illustrating Drew Reese's suggestion. I nixed resetForm in favor of calling your state setters directly in the useEffect hook. These functions can be excluded from the dependency array because React guarantees their consistency.
If you wanted to include the resetForm() function in your effect hook, you should construct as a memoized function with the useCallback() hook to prevent infinite re-renders.
Also, fwiw, if useEffect is routing the user to another page in your app, this component will unmount and you don't need to worry about resetting the email and password states.
const SignIn = (props) => {
const { signInSuccess } = useSelector(mapState);
const dispatch = useDispatch();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
if (signInSuccess) {
setEmail("");
setPassword("");
props.history.push("/");
}
}, [signInSuccess, props.history]);
};

Can't perform a React state update on an unmounted component error when fetching data

I am having an issue when I am trying to fetch some data. For some reason, I keep receiving 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 a useEffect cleanup function.
This is the entire component, where I am fetching the data and then passing it in to the drawerconfig component This data then gets passed down further and further in to other components:
export default function Root() {
const [userImage, setUserImage] = useState();
const [userName, setUserName] = useState();
const [name, setName] = useState();
const [urlName, setUrlName] = useState();
const [userID, setUserID] = useState();
const [followerCount, setFollowerCount] = useState();
const [followingCount, setFollowingCount] = useState();
const [links, setLinks] = useState([{link: null}]);
const [pageLoading, setPageLoading] = useState(true);
// Following counts, displayname, image
const fetchData = useCallback((data) => {
const dataRef = firestore().collection('usernames');
const usersLinks = firestore().collection('links');
// Fetch user Links
usersLinks.doc(data.data().urlName).onSnapshot((doc) => {
const entries =
doc.data() === undefined ? [undefined] : Object.values(doc.data());
entries[0] === undefined ? setLinks([{link: null}]) : setLinks(entries);
});
dataRef.doc(data.data().urlName).onSnapshot((snap) => {
// Fetch users image
setUserImage(snap.data().imageUrl);
setUserID(snap.data().displayName);
setUserName(snap.data().userName);
setUrlName(data.data().urlName);
setName(snap.data().displayName);
setFollowerCount(snap.data().followers);
setFollowingCount(snap.data().following);
setPageLoading(false);
});
}, []);
// Fetch all data here
useEffect(() => {
auth().onAuthStateChanged((user) => {
if (user !== null) {
if (user.emailVerified) {
const cleanup = firestore()
.collection('users')
.doc(user.uid)
.onSnapshot(fetchData);
return cleanup;
}
}
});
}, [fetchData]);
return (
<>
{/* ALL SCREENS */}
{pageLoading ? (
<ActivityIndicator size="large" color="black" />
) : (
<DrawerConfig
links={links}
username={userName}
userimage={userImage}
userid={userID}
displayname={name}
urlname={urlName}
followerCount={followerCount}
followingCount={followingCount}
/>
)}
</>
);
}
Any help would be appreciated, Thank you
Looks like you need to modify your useEffect a bit - I don't think your listener is being unsubscribed when you unmount this component.
// Fetch all data here
useEffect(() => {
return auth().onAuthStateChanged((user) => {
...
})
})
.onAuthStateChanged() returns the unsubscribe function; useEffect accepts an unsubscribe function as a return to be executed on unmount.

Can't perform a React state update on an unmounted component.(UseEffect)(Context API)(REACT.js)

So I am getting this 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 a useEffect cleanup function.
in Products (created by Context.Consumer)*
Well, it's occurring in the Products Component When I Reroute to Product Edit component!
Products component is used to list all products and product edit is used to edit product details and both these components are connected to the same context API using useContext.
My Context API provider looks like this
*import React, { useState , createContext , useEffect} from 'react';
import firebase from "../firebase";
export const ProdContext = createContext();
const ProdContextProvider = props => {
const [products , setproducts]= useState([])
const [loading , setloading] = useState(true)
const [subcribe , setsubcribe] = useState(false)
const unsub =()=>{
setsubcribe(false);
console.log("unsubcribe--"+subcribe)
}
const sub =()=>{
setsubcribe(true);
console.log("subcribe--"+subcribe)
}
useEffect(() => {
let mounted = true;
setloading(true)
async function fetchData() {
setloading(true);
await firebase.firestore()
.collection("products")
.onSnapshot((snapshot)=>{
const data = snapshot.docs.map((doc)=>(
{
id : doc.id,
...doc.data()
}
))
console.log("b4 if--"+subcribe)
if(subcribe){
console.log("in if--"+subcribe)
setproducts(data)
setloading(false)
}
})
}
fetchData();
return () => mounted = false;
}, [subcribe])
console.log("after getting bottom"+subcribe)
return (
<ProdContext.Provider value={{subcribe:subcribe,prodloading:loading, products: products, loading:loading , sub:sub , unsub:unsub}}>
{props.children}
</ProdContext.Provider>
);
}
export default ProdContextProvider;*
And my products Component looks like this:
export default function Products(props){
const {products , loading ,sub , unsub,subcribe}= useContext(ProdContext)
const [selectid, setselectid] = useState("")
const [ShowLoading, setShowLoading] = useState(true);
const [showAlert2, setShowAlert2] = useState(false);
const [redirect , setredirect] = useState(false)
const [value, setValue] = useState(null);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
sub();
console.log("product mound--"+subcribe)
}, [])
useEffect(() => {
return () => {
console.log("product unsub--"+subcribe)
unsub();
console.log("product unmound--"+subcribe)
};
}, []);
if (redirect) {
return <Redirect push to={{
pathname: `/products/${selectid}`,
}} />;
}
return ( .........)}
Product Edit Component:
const Productedit = (props) => {
const {products,loading , subcribe} = useContext(ProdContext);
const { sub,unsub } = useContext(ProdContext);
const [formData, setformData] = useState({});
const {category} = useContext(CatContext)
const [showLoading, setShowLoading] = useState(false);
const [mainurl, setmainurl] = useState(null);
const [imggal, setimggal] = useState([]);
const [situation , setsituation] = useState("")
const [redirect , setredirect] = useState(false)
const [showAlert, setShowAlert] = useState(false);
const [msg, setmsg] = useState(true);
useEffect(() => {
sub();
console.log("productedit mound--"+subcribe)
return () => unsub();
}, [])
...........
Well I think the issue is that products component is still subscribed to getproduct provider even when it is unmounted but I cant get to solve the issue anyone can help
Error message Details and console log?
The issue is not related to Firestore rather this seems to be a common issue with react.
We just no need to update the state in the callback if the component is not mounted already.
It's better to check before updating the state by adding an if block in the async function
if(mounted) {// Code inside the async tasks}
Please refer here [1] for additional information about this warning.
[1] https://www.debuggr.io/react-update-unmounted-component/#state-updates
(This should have been a comment as I don't have enough reputation hence posting as an answer)
Hope I understood the question right and the suggestion helps!
You are not using mounted except only initializing it, you should not update your react state if your component is unmounted. You should do this inside your callback after successful API request:
if(mounted){
// your state update goes here
}
But this is not the right though, what you should do is cancel your API request when component unmounts, I know little bit about request cancellation while using axios. You should search the web about API request cancellation in firebase or whatever you are using because if you are not updating your react state if component unmounts then still API request continue to run in the background and that is the main issue and that's why react throws this warning.

Resources