I'm using the firebase NPM package with Next.JS/React/Typescript. From what I can tell, there are two ways of watching when a user changes:
useEffect(() => {
// do something
}, [firebase.auth().currentUser])
and something like
const onAuthStateChanged = () => {
return firebase.auth().onAuthStateChanged((user) => {
// do something
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged();
return () => {
unsubscribe();
};
}, [])
What's the difference here? They both seem to just watch the currentUser; is one preferable over the other?
This will run everytime currentUser changes.
useEffect(() => {
// do something
}, [firebase.auth().currentUser])
This only runs the first time your component mounted. You create a listener to handle changes and remove it when component unmounted.
useEffect(() => {
const unsubscribe = onAuthStateChanged();
return () => {
unsubscribe();
};
}, [])
Related
tldr: the await call inside a useEffect hook doesn't resolve itself until after the component starts to unmount, it just hangs until that happens. Not sure why this is happening or how to debug it. This is in a react-native expo project. Swapping the functional component out with a class based one works as expected.
given the following useEffect calls in an expo project
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff();
console.log('fetched');
};
fetch();
}, [depA, depB]);
What I'm seeing in the console when the component is mounted is
'mount'
'fetching'
then when the component is unmounted I see
'unmount'
'fetched'
For some reason, the await call doesn't resolve until the component is unmounted. I've used this pattern in other parts of my code seemingly without issue so I can't figure out why this is happening here. When I swap the functional component out with a class it's working as expected. Any ideas on why this is happening? It looks like the fetchStuff call is being deferred until the component is about to unmount. Swapping fetchStuff out with await new Promise((res) => res(null)); doesn't seem to make any difference
Full component looks something like
function WhatIsHappening({depA, depB}) {
const [stuff, setStuff] = useState([])
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff(depA, depB);
console.log('fetched');
setStuff(stuff)
};
fetch();
}, [depA, depB]);
return (
<View>
<ListStuff stuff={stuff}></ListStuff>
<View>
)
}
There is something wrong with fetchStuff. This is a working version.
async function fetchStuff() {
return new Promise((resolve) => resolve("fetched"));
}
Working Sandbox
I have an API that needs to be fetched every 30 seconds.The UseEffect written below in the code is for a component that gets rendered on home component. It's working well but if I navigate to any another page I need the API to not be fetched.
I'm using react-router and redux.
useEffect(() => {
dispatch(loadCurrencyList())
setInterval(() => {
dispatch(loadCurrencyList())
}, 30000
)
}, [dispatch])
Do a cleanup in the return of useEffect :
useEffect(()=>{
const timer = setInterval(...)
return ()=> clearInterval(timer)
}, [dispatch])
Store timer id in a mutable variable created with useRef() hook,
then use it in the component unmount code. Don't return clean up function from useEffect() dependent on [dispatch] as it will be executed every time dispatch variable changes.
const timer = useRef();
useEffect(() => {
dispatch(loadCurrencyList());
timer.current = setInterval(() => {
dispatch(loadCurrencyList())
}, 30000);
}, [dispatch]);
useEffect( () => () => {
if (timer.current) {
clearInterval(timer.current);
}
}, []);
I want to know how to run the useEffect side effect in both component mounting and a dependent value change. Currently I'm using two useEffects to achieve this like this.
useEffect(() => {
let isMounted = true;
const getUsers = async () => {
try {
const userResponse = await api.get('/users');
if (isMounted) { setUsers(userResponse.data); }
} catch (error) {
console.log(error);
}
};
getUsers();
}, []);
useEffect(() => {
let isMounted = true;
const getUsers = async () => {
try {
const userResponse = await api.get('/users');
if (isMounted) { setUsers(userResponse.data); }
} catch (error) {
console.log(error);
}
};
getUsers();
}, [netInfo]);
Is there anyway to achieve this using one useEffect?
Runs when the component is mounted for the first time and on every re-render
useEffect(() => {})
Runs when the component is mounted for the first time alone
useEffect(() => {}, [])
Runs when the component is mounted for the first time and whenever the someDependency's value changes .
useEffect(() => {}, [someDependency])
You can remove the first useEffect .
I cannot find how to solve one issue could anyone help
wrote simple hook for catching shortcuts but callback from that custom hook doesn't see new props in component
export const useShortcut = (key, isAlt = false, callback) => {
function onKeyPressed(event) {
if (event.key.toLowerCase() === key && event.altKey === isAlt) {
callback();
}
}
useEffect(() => {
window.addEventListener("keydown", onKeyPressed);
return () => {
window.removeEventListener("keydown", onKeyPressed);
};
}, []);
};
export const MyComponent = (props) => {
function handleShortcut() {
if (props.prop1) {
//???prop1 came from parent and components tab tell me but from shortcut hook it always null
}
}
useShortcuts("n", true, () => handleShortcut());
return <div></div>;
};
many thanks in advance
useEffect(() => {
window.addEventListener("keydown", onKeyPressed);
return () => {
window.removeEventListener("keydown", onKeyPressed);
};
}, []);
Because of the empty dependency array, you are only setting up the listener once, using whatever onKeyPressed exists on the first render. That function closes over props from the first render, and it will never update.
Simplest fix for this is just to remove the dependency array, so the effect runs every time:
useEffect(() => {
window.addEventListener("keydown", onKeyPressed);
return () => {
window.removeEventListener("keydown", onKeyPressed);
};
});
Now when the component renders, it will tear down the old listener and create a new one with the new copy of onKeyPressed. That new function sees the new props.
Hypothetically, if setting up and tearing down the listeners was an expensive operation (it's not), then you could limit it to only happen when onKeyPressed changes, by putting onKeyPressed into the dependency array. However, this needs to be accompanied by using useCallback to make sure onKeyPressed doesn't change unless it needs to.
useEffect(() => {
window.addEventListener("keydown", onKeyPressed);
return () => {
window.removeEventListener("keydown", onKeyPressed);
};
}, [onKeyPressed]);
// used like:
export const MyComponent = (props) => {
const handleShortcut = useCallback(function () {
if (props.prop1) {
// ...
}
}, [props.prop1]);
useShortcuts("n", true, handleShortcut);
I have a specific case. The first thing I do is request the Index.DB. After I got the taskId from it, I need to start asking the server every 5 seconds. And stop doing this on a specific flag. How can i do that properly with hooks?
I'tried to use useInterval hook like this:
https://github.com/donavon/use-interval;
But when i set it in useEffect causes consistent error:
Invalid hook call. Hooks can only be called inside of the body of a function component.
const Page = () => {
const [task, setTask] = useState({})
const isLoaded = (task.status === 'fatal');
const getTask = (uuid: string) => {
fetch(`${TASK_REQUEST_URL}${uuid}`)
.then(res => {
return res.json();
})
.then(json => {
setTask(json.status)
})
.catch(error => console.error(error));
};
useEffect(() => {
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
}
useInterval(() => getTask(taskId), 5000, isTaskStatusEqualsSomthing)
})
}, []);
return (
<p>view</p>
);
};
I also tried to play around native setInterval like this
useEffect(() => {
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
}
setInterval(() => getTask(taskId), 5000)
})
}, []);
But in this case i don't know how to clearInterval and also code looks dirty.
The solution is simple. You just need to configure your setInterval within .then callback like
useEffect(() => {
let timer;
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
else {
timer = setInterval(() => getTask(taskId), 5000)
}
}
})
return () => {clearInterval(timer)}
}, []);
The reason, first approach doesn't work for you is because you cannot call a hook conditionally or in useEffect as you did for useInterval