I have one customer component. And it's getting called twice. One to create the customer and the second to edit it. When I call this component first-time values are getting initialized and updated correctly. When I render a component a second time for editing customer values are not getting updated via the use effect.
const AddCustomer = (props) => {
console.log("Props",props)
const {customerId} = useParams();
const [customerName, setcustomerName] = useState("");
const [customerKey, setcustomerKey] = useState("");
const [formErrors, setFormErrors] = useState({});
const [apiError, setApiError] = useState(null);
const [isSubmit, setIsSubmit] = useState(false);
const [loadSpinner, setLoadSpinner] = useState(false);
useEffect(() => {
if(props.isEdit) {
populateExistingFormData();
}
if (Object.keys(formErrors).length === 0 && isSubmit) {
callAPI();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formErrors, isSubmit, props.isEdit, props]);
const populateExistingFormData = () => {
setCustomerId(props.customerId)
setCustomerName(props.serviceProviderCode)
setKey(props.key)
setIsSubmit(props.isSubmit); //false value from props
setLoadSpinner(props.loadSpinner);
} }
As I am passing submit value as a false from props. But it's set up as true again (value showing from previous component render) and its calls then call API. how can I update the state on component re-render?
Related
I'm doing a social networking project on React
I wanted to replace one component from class - to functional and use hooks, and a global problem appeared:
When I go to a new user, the page displays the status of the previous one
I use useState() hook, debugged everything, but for some reason when a new status component is rendered, it doesn't update
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
I thought the problem was that the container component was still a class component, but by redoing it, nothing has changed
One way to solve this is by using the useEffect hook to trigger an update when props change. You can use the hook to do comparison between current props and previous props, then update status in the state.
Use this as reference and adapt according to your own code.
const ProfileStatus = (props) => {
const [edditMode, setEdditMode] = useState(false);
const [status, setValue] = useState(props.status || "Empty");
useEffect(() => {
setValue(props.status || "Empty");
}, [props.status]);
const onInputChange = (e) => {
setValue(e.target.value);
};
const activateMode = () => {
setEdditMode(true);
};
const deactivateMode = () => {
setEdditMode(false);
props.updateUserStatus(status);
};
I understand that without dependencies you cannot access state inside useEffect. An example as such :
const [state, setState] = React.useState('')
const ws = new WebSocket(`ws://localhost:5000`)
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[])
In this case state comes back as empty even though it has changed in other parts of the code. To solve this in principle I should add state as dependency to the useEffect hook. However, this will trigger the listener again and I do not want to have 2 active listeners on my websocket or being forced to close and reopen it again as such :
React.useEffect(() => {
ws.onopen = () => {
console.log(state)
},[state])
What is good practice when it comes to accessing state inside a listener that sits inside useEffect hook?
IF you need to re-render the component when the state changes try this:
const [state, setState] = React.useState('');
const stateRef = React.useRef(state);
React.useEffect(() => {
stateRef.current = state;
}, [state])
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
This way you create the ws only once, and it will use the state reference which will be up to date because of the useEffect.
If you don't need to re-render the component when the state updates you can remove const [state, setState] = React.useState(''); and the useEffect and just update the stateRef like this when you need.
Like this:
const stateRef = React.useRef(null);
const ws = React.useMemo(() => {
const newWS = new WebSocket(`ws://localhost:5000`);
newWS.onopen = () => {
console.log(stateRef.current);
}
return newWS;
}, []);
// Update the state ref when you need:
stateRef.current = newState;
Best Practice get data in listeners.[UPDATED]!
const [socketData , setSocketData] = useState(null);
useEffect(() => {
websocet.open( (data) => {
setSocketData(data);
})
},[])
//second useEffect to check socketData
useEffect(() => {
if(socketData){
// access to data which come from websock et
}
},[socketData])
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.
I am trying to update the state conditionally to trigger visibility.
However when I pass a initial value to the useState hook, it updates only once.
Code 1: Updating only once when there is an initial value
const ImageRef = useRef(null);
// Initial Value Passed
const [isHidden, setShown] = useState(true);
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
if (isHidden !== nextState) {
setShown(nextState);
}
};
Code 2: Working as expected when there is no initial value
const ImageRef = useRef(null);
// No Initial Value Passed
const [isHidden, setShown] = useState();
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
if (isHidden !== nextState) {
setShown(nextState);
}
};
I guess _HandleShown is use in addEventListener..., in that case you want to use isHidden to compare you need to do this, because of closure in JS.
const ImageRef = useRef(null);
// Initial Value Passed
const [isHidden, setShown] = useState(true);
const _HandleShown = () => {
const nextState = window.scrollY > ImageRef.current.offsetHeight;
setShown(prevIsHidden =>{
if (prevIsHidden !== nextState) {
return nextState
}
return prevIsHidden
});
};
I am dispatching action in side useEffect after dispatching I want to get state from redux state and pass this to local state inside component But the problem is whenever i try to do this It either goes to infinite loop or doesn't setState at all. I don't know how to solve this. Any help would be great.
This is my code.
const [tableData, setTableData] = React.useState([]);
const DataReceived = (state) => <--- Here I am getting state from store.
state.AllUsers.Seller.sellerDetails.data._embedded;
const selectedData = useSelector(DataReceived, shallowEqual);
const selectedDataAgain = selectedData
? selectedData.vendorUserResourceList
: null;
console.log("selectedDataAgain", selectedDataAgain); <--- this one is working this shows array of data.
console.log("selectedDataAgainTable", tableData);
const { GetUserLoadVendors } = props;
React.useEffect(() => {
const access_token = localStorage.getItem("access_token");
GetUserLoadVendors(access_token); <--- this is the actions
setTableData(selectedDataAgain); <--- here am trying to set State
}, []);
When i add optional second argument in useEffect like [GetUserLoadVendors, selectedDataAgain] it goes to infinite loop. If i don't add any dependency it doesn't setStates.
Bring selectedDataAgain into useEffect and includes selectedData as dependency
const [tableData, setTableData] = React.useState([]);
const DataReceived = (state) => <--- Here I am getting state from store.
state.AllUsers.Seller.sellerDetails.data._embedded;
const selectedData = useSelector(DataReceived, shallowEqual);
console.log("selectedDataAgainTable", tableData);
const { GetUserLoadVendors } = props;
React.useEffect(() => {
const selectedDataAgain = selectedData
? selectedData.vendorUserResourceList
: null;
console.log("selectedDataAgain", selectedDataAgain); // this one is working this shows array of data.
const access_token = localStorage.getItem("access_token");
GetUserLoadVendors(access_token); <--- this is the actions
setTableData(selectedDataAgain); <--- here am trying to set State
}, [selectedData]);
I don't think you want useEffect to trigger when GetUserLoadVendors change, if you want so, want can includes GetUserLoadVendors s a dependency but make sure you want wrap it with useCallback at the place it is created (parent component).
You ideally do not need copy data from props into state since its directly derivable from redux state.
However if you need to, you should write the update logic in separate useEffect like
const [tableData, setTableData] = React.useState([]);
const DataReceived = (state) =>
state.AllUsers.Seller.sellerDetails.data._embedded;
const selectedData = useSelector(DataReceived, shallowEqual);
const selectedDataAgain = selectedData
? selectedData.vendorUserResourceList
: null;
console.log("selectedDataAgain", selectedDataAgain);
console.log("selectedDataAgainTable", tableData);
const { GetUserLoadVendors } = props;
React.useEffect(() => {
const access_token = localStorage.getItem("access_token");
GetUserLoadVendors(access_token);
}, []);
React.useEffect(() => {
setTableData(selectedDataAgain);
}, [selectedData]) // Dependency is selectedData and not selectedDataAgain since reference of selectedDataAgain changes on every render whereas it doesn't for selectedData