I am building a reactjs app
I want to get my user id from redux state
using useSelector and pass it to useEffect
My code
UserReducer
export function userReducer(state = { token: "", user: {} }, action) {
switch (action.type) {
case "LOGIN":
return action.payload;
case "LOGOUT":
return action.payload;
default:
return state;
}
}
ViewTransaction.jsx
const ViewTransaction = () => {
const user_id = useSelector((state) => state.user.user.id);
const params = useParams();
useEffect(() => {
setLoading(true);
PendingServices.getPendingTransaction(
`localhost:8000/user/transaction/pending/get-transaction/${params.id}?user_id=${user_id}`
)
.then((response) => {})
.catch((error) => {})
.finally(() => {
setLoading(false);
});
}, []);
return (
<div>transaction</div>
)
}
export default ViewTransaction;
user_id is showing undefined.
You need to pass user_id in useEffects dependency array and check if its defined or nod... because on first render its undefined because redux didnt provided it yet
useEffect(() => {
if(user_id){
setLoading(true);
PendingServices.getPendingTransaction(
`localhost:8000/user/transaction/pending/get-transaction/${params.id}?user_id=${user_id}`
)
.then((response) => {})
.catch((error) => {})
.finally(() => {
setLoading(false);
});
}
}, [user_id]);
Like Drew wrote in the comment above your dependency array is missing variables. React tries to be smart about not calling hooks in vain, and by passing it no dependencies you let it assume that the call to the method in useEffect will always be the same, and that it can cache and reuse the result of the invocation. Since user_id is undefined initially, when no one have logged in, the result of useEffect with an undefined user is cached.
By adding user_id and params.id in the dep array you're telling React that "each time these variables change, the result of the method should also change", so it will invalidate the cached useEffect result and run the method again.
I'd recommend using this eslint plugin to automatically help catch these cases: https://www.npmjs.com/package/eslint-plugin-react-hooks
Related
In my react app, I am making a request to an API inside useEffect. Once the promise is returned, I am setting state. I then need to use that state to subscribe to a websocket, but state is coming back as undefined so for now I am just logging the state. I only want to do this on initial page load. How can I make an API request, save that information to state, then use that state to do something all on initial page load only?
const fetchInstruments = () => {
return axios
.post('http://localhost:5050/0/public/AssetPairs')
.then(({ data }) => {
return data.result
})
.catch((error) => console.log(error))
}
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
})
.then(()=> console.log(instruments)) //this is undefined
}, [])
}
if I were in your place, I would not use the state to subscribe to a websocket, I will use the result of the HTTP request
checkout this
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments);
return fetchedInstruments; // <======
})
.then((fetchedInstruments) => {
console.log(fetchedInstruments);
//TODO : handle websocket subscription
})
}, [])
}
You can just make your second API call when you fetched your instruments:
function App() {
const [instruments, setInstruments] = useState()
useEffect(() => {
fetchInstruments().then((fetchedInstruments) => {
setInstruments(fetchedInstruments)
secondApiCall(fetchedInstruments); // You can do your second API call here
})
}, [])
}
I have a method which would use the value from useSelector and another dispatch which would update my value from the useSelector, however, it seems the value does not get updated after dispatch, for example
const userProfile = (props) => {
const hasValidationError = useSelector(state => {
state.hasValidationError;
}
const dispatch = useDispatch():
const updateProfile = async (userId) => {
dispatch(startValidation()); // <-- this would change the hasValidationError in state
if (hasValidationError) {
console.log('should not update user');
await updateUser(userId);
dispatch(showSuccessMsg());
} else {
conosole.log('can update user');
}
}
}
The hasValidationError would always be false, even if the value did changed from state, how could I get the updated value immediately after dispatch(startValidation()) ?
I also tried something different, like creating a local state value to monitor my global state by using useState() and useEffect()
const [canUpdateUser, setCanUpdateUser] = useState(false);
useEffect(() => {
console.log('useEffect hasValidationError :>> ', hasValidationError);
setCanUpdateUser(!hasValidationError);
}, [hasValidationError]);
Then use canUpdateUser as my conditional flag in updateProfile (if (canUpdateUser)), however, this seems to work only the first time when validation triggers, but after that, the canUpdateUser value is always the old value from my updateProfile again...
How could I resolve this? Is there any way to ensure getting updated value from global state after certain dispatch fires?
Could you maybe try from a slightly different approach (combining both) since it seems you want to be listening on changes of hasValidationError, using a useEffect with a dependency on that variable can maybe resolve your issue.
const userProfile = (props) => {
const { hasValidationError } = useSelector(state => state);
const dispatch = useDispatch():
const [userId, setUserId] = useState();
const updateProfile = async (userId) => {
dispatch(startValidation());
setUserId(userId);
};
useEffect(() => {
if (hasValidationError) {
console.log('should not update user');
await updateUser(userId);
dispatch(showSuccessMsg());
} else {
conosole.log('can update user');
}
}, [hasValidationError]);
}
Wherener I add more that one dispatch to useEffect() it causes all of the following to fire twice. Example below.
Here is the full code. Remove comments in any other order you want
ps. idk how to make it run as a code snippet
pps. I've used this question as an example
import React, { useReducer, useEffect } from "react";
const url = `https://picsum.photos/v2/list?page=3&limit=1`;
function reducer(data, action) {
console.log('reducer triggered', action.type);
switch (action.type) {
case "INITIALIZE":
console.log(action.payload, "Initialize");
return action.payload;
case "ADD_NEW":
const newData = { ...data };
newData.info = newData.info || [];
newData.info.push({});
console.log(newData);
return newData;
case "INI2":
return action.payload;
case "INI3":
return action.payload;
default:
return
}
}
function App() {
const [data, dispatch] = useReducer(reducer, null);
useEffect(() => {
//dispatch({type: "INI2"}); // uncommment this to get 2 reducer messages from each below
console.log("here");
fetch(url)
.then(async response => {
dispatch({type: "INITIALIZE",payload: (await response.json())});
//dispatch({type: "INI3"}); // uncomment this to see that every new dispatch is doubled after first one
})
.catch(error => {console.log(error);});
}, []);
const addNew = () => {
dispatch({ type: "ADD_NEW" });
};
return (
<>
<div>{data ? JSON.stringify(data) : "No Data Yet"}</div>
<button onClick={addNew}>Test</button>
</>
);
}
export default App
This is just a result of React.StrictMode being present in your App
According to the React Documentation
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the functions
And Functions passed to useState, useMemo, or useReducer are part of double invocation, which is why you see that behavior in your case
Expected Working demo without React.StrictMode
I am trying to create a component that allows detecting changes in the redux store. Once the shouldUpdateData flag is set in the store, the component responsible for updating should fetch the data by using an async action creator. In my case, either the error "Maximum updates have reached" occurs or the update never happens.
Depending on the dispatch function stopFetching() (turns off the shouldUpdateData flag), the error or outcome changes. If I do the dispatch inside the action creator there are endless updates. If the code is used as it is below, no update occurs.
The reason I used the useSelector() hook from 'react-redux' is to detect a change in the store for the loading attribute.
Thank you in advance.
Here is the action creator:
export function updateDataAsync(id) {
return function (dispatch) {
// dispatch(fetchDataRequest());
return fetch(`/api/user/${id}/data`, {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(
(result) => {
let {projects, notes} = result;
// New data and dispatch function
dispatch(fetchDataSuccess({projects, notes}));
},
(error) => { dispatch(fetchDataFailure(error)) }
)
}
}
Here is the reducer for this action creator:
export function savedData(state = DATA_INITIAL_STATE, action) {
switch(action.type) {
case FETCH_STATES.FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_STATES.FETCH_DATA_SUCCESS:
return {
loading: false,
data: action.data,
error: ''
}
case FETCH_STATES.FETCH_DATA_FAILURE:
return {
loading: false,
data: {},
error: action.error.message
}
default:
return state;
}
}
The React component that is doing the update:
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
let reqSent = useRef(false);
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
})
return loading ? <LoadingAnimation /> : children;
}
const mapStateToProps = (state) => {
return {
userId: state.user.id,
shouldUpdate: state.shouldUpdateData // The flag that should trigger the update
}
}
const mapDispatchToProps = (dispatch) => {
return {
stopFetch: () => { dispatch(setShouldFetchData(false)) },
update: (id) => { dispatch(updateDataAsync(id)) },
startFetch: () => dispatch(fetchDataRequest()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(StoreUpdater);
You dint pass any dependency to useEffect so it will be called on every render which is causing infinite renders
change useEffect to
useEffect(()=>{
if(!reqSent && shouldUpdate) {
startFetch();
update(userId)
reqSent.context = true;
}
},[])
For complete information regarding useEffect refer this link
The reference I created inside the component responsible of the updates, was causing the problem. The reference was preventing the update dispatch to occur due to the if statement being false.
mapStateToProps and mapDispatchToProps were react-redux higher order functions to connect classes components into the store. there equalants at functional components are useSelector and useDispatch. re-write your HOC redux adaption into hooks, and add [ dependency ] at useEffect usage
function StoreUpdater({ update, userId, shouldUpdate, startFetch, stopFetch, children }) {
const loading = useSelector(state => state.savedData.loading);
const userId = useSelector(state => state.user.id);
const shouldUpdate = useSelector(state => state.shouldUpdateData);
let reqSent = useRef(false);
const dispatch = useDispatch() // import from 'react-redux'
useEffect(()=>{
if(!reqSent && shouldUpdate) {
dispatch(startFetch());
dispatch(update(userId));
reqSent.context = true;
}
}, [reqSent, shouldUpdate, startFetch, dispatch, update, userId])
return loading ? <LoadingAnimation /> : children;
}
export default StoreUpdater ;
I'm trying to get all data with the 'public' parameter of a firebase collection and then use useffect, but the console accuses error.
I took this structure from firebase's documentation:
https://firebase.google.com/docs/firestore/query-data/get-data#get_multiple_documents_from_a_collection
but the console says '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'
So I went to this other page:
https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener
But I'm not using onSnapshot, besides firebase documentation seems wrong to me, since unsub is not a function.
useEffect(() => {
let list = []
const db = firebase.firestore().collection('events')
let info = db.where('public', '==', 'public').get().then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
setLoading(false)
return;
}
snapshot.forEach(doc => {
console.log(doc.id, "=>", doc.data())
list.push({
id: doc.id,
...doc.data()
})
})
setEvents(list)
setLoading(false)
})
.catch(error => {
setLoading(false)
console.log('error => ', error)
})
return () => info
}, [])
You need to invoke the listener function to detach and unmount.
https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener
useEffect(() => {
let list = []
const db = firebase.firestore().collection('events')
let info = ...
return () => info() # invoke to detach listener and unmount with hook
}, [])
You don't need to clear a .get(), it's like a normal Fetch api call.
You need to make sure your component is still mounted when you setLoading
Example:
// import useRef
import { useRef } from 'react'
// At start of your component
const MyComponent = () =>{
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, [])
// Your query
db.where('public', '==', 'public').get().then(snapshot => {
if(isMounted.current){
// set your state here.
}
})
return <div></div>
}