how do i update a navbar on an api call (specifically a google signin) - javascript

i'm having a tough time understanding the hook concept in this case:
the user signs in through google, updating the session state.
this updates a visitorType state from 'viewer' to 'buyside'...
and then my navbar is supposed to adjust. but it doesn't, unless switch from the browser to another app and then back again...
const { data: session } = useSession(); //next-auth login sessions
const [visitorType, setVisitorType] = useState('viewer')
const visitorRef = useRef(visitorType);
useEffect(()=>{
return(
() => {if (session) {
visitorRef.current='buyside';
setVisitorType('buyside')}
}
)
},[session])
from what i understand, useRef gets the value of something between re-rerenders? and useEffect is asynchronous, so it lags behind? - so, not sure how i can update a reference..

The function returned by useEffect fires when the components unmounts. I think what you want is:
useEffect(()=>{
if (session) {
visitorRef.current='buyside';
setVisitorType('buyside')}
}
},[session])
Regarding your question on useRef:
when you set visitorRef.current. No rerender is triggered.
when you call setVisitorType, a rerender happens

Related

React JS: Why is my cleanup function triggering on render? [duplicate]

I have a counter and a console.log() in an useEffect to log every change in my state, but the useEffect is getting called two times on mount. I am using React 18. Here is a CodeSandbox of my project and the code below:
import { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(5);
useEffect(() => {
console.log("rendered", count);
}, [count]);
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount(count + 1)}> click to increase </button>
</div>
);
};
export default Counter;
useEffect being called twice on mount is normal since React 18 when you are in development with StrictMode. Here is an overview of what they say in the documentation:
In the future, we’d like to add a feature that allows React to add and remove sections of the UI while preserving state. For example, when a user tabs away from a screen and back, React should be able to immediately show the previous screen. To do this, React will support remounting trees using the same component state used before unmounting.
This feature will give React better performance out-of-the-box, but requires components to be resilient to effects being mounted and destroyed multiple times. Most effects will work without any changes, but some effects do not properly clean up subscriptions in the destroy callback, or implicitly assume they are only mounted or destroyed once.
To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
This only applies to development mode, production behavior is unchanged.
It seems weird, but in the end, it's so we write better React code, bug-free, aligned with current guidelines, and compatible with future versions, by caching HTTP requests, and using the cleanup function whenever having two calls is an issue. Here is an example:
/* Having a setInterval inside an useEffect: */
import { useEffect, useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((count) => count + 1), 1000);
/*
Make sure I clear the interval when the component is unmounted,
otherwise, I get weird behavior with StrictMode,
helps prevent memory leak issues.
*/
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
export default Counter;
In this very detailed article called Synchronizing with Effects, React team explains useEffect as never before and says about an example:
This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs. From the user’s perspective, visiting a page shouldn’t be different from visiting it, clicking a link, and then pressing Back. React verifies that your components don’t break this principle by remounting them once in development.
For your specific use case, you can leave it as it's without any concern. And you shouldn't try to use those technics with useRef and if statements in useEffect to make it fire once, or remove StrictMode, because as you can read on the documentation:
React intentionally remounts your components in development to help you find bugs. The right question isn’t “how to run an Effect once”, but “how to fix my Effect so that it works after remounting”.
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
/* As a second example, an API call inside an useEffect with fetch: */
useEffect(() => {
const abortController = new AbortController();
const fetchUser = async () => {
try {
const res = await fetch("/api/user/", {
signal: abortController.signal,
});
const data = await res.json();
} catch (error) {
if (error.name !== "AbortError") {
/* Logic for non-aborted error handling goes here. */
}
}
};
fetchUser();
/*
Abort the request as it isn't needed anymore, the component being
unmounted. It helps avoid, among other things, the well-known "can't
perform a React state update on an unmounted component" warning.
*/
return () => abortController.abort();
}, []);
You can’t “undo” a network request that already happened, but your cleanup function should ensure that the fetch that’s not relevant anymore does not keep affecting your application.
In development, you will see two fetches in the Network tab. There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned... So even though there is an extra request, it won’t affect the state thanks to the abort.
In production, there will only be one request. If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
function TodoList() {
const todos = useSomeDataFetchingLibraryWithCache(`/api/user/${userId}/todos`);
// ...
Update: Looking back at this post, slightly wiser, please do not do this.
Use a ref or make a custom hook without one.
import type { DependencyList, EffectCallback } from 'react';
import { useEffect } from 'react';
const useClassicEffect = import.meta.env.PROD
? useEffect
: (effect: EffectCallback, deps?: DependencyList) => {
useEffect(() => {
let subscribed = true;
let unsub: void | (() => void);
queueMicrotask(() => {
if (subscribed) {
unsub = effect();
}
});
return () => {
subscribed = false;
unsub?.();
};
}, deps);
};
export default useClassicEffect;

Why is useQuery making my component re-render when I come back to the tab?

I'm using react-query library, and I looked it up, and useQuery can make your react component re-render because it has states, but the weird thing is that it re-renders when I change tabs (like, I go to youtube, then come back to my app and the component just re-renders). But it only happens with useQuery, and I don't understand why.
Edit: It also happens when I click my console and then click the app again
The code is very simple:
const { data } = useQuery("pokemon", () =>
axios("https://pokeapi.co/api/v2/pokemon/")
);
console.log(data);
It's literally happening right now. Every time I go back to my app, it just logs the data again. I don't know what I'm missing
If you're using the react-query library, consider setting the refetchOnWindowFocus option to false
So your code should look like this
const { data } = useQuery("pokemon", () =>
axios("https://pokeapi.co/api/v2/pokemon/"),
{
refetchOnWindowFocus: false
}
);
console.log(data);
You can also set a refetchInterval option in milliseconds to only refetch at certain time intervals

Updating the user coming from react-with-firebase-auth

I built an app starting from a super easy example on medium.
To summarize, it appears the user comes into my app at the bottom part. I then pass the user down through props:
function App({user)){
return <AppContainer user={users} />
}
export default withFirebaseAuth({
providers,
firebaseAppAuth,
})(App);
Later in my app I want to update the user:
function AppContainer( { signOut, user } ){
const [user, setUser] = useState(user);
firebase.firestore().collection('users').doc(user.uid).get().then(function(doc) {
if (doc.exists) {
setUser(doc.data())
But it doesn't work. What's the Best way to do this?
Additionally it would be ideal if the two API calls could be combined somehow.
Last question is - I don't really understand how the export part of the code works - is it like context and i should be able to access the data coming in as props all the way down? Can I refactor it into context?
Usually for similar API calls useEffect hook is used, as documentation states:
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
First I would add the firebase calls into that hook then secondly I would change the user variable's name, you have one coming through props and similarly you are defining with useState.
I would consider using as the following:
function AppContainer( { signOut, user } ){
const [currentUser, setCurrentUser] = useState(user);
useEffect(() => {
firebase.firestore().collection('users').doc(currentUser.uid).get().then(function(doc) {
if (doc.exists) {
setUser(doc.data());
}
});
}, []);
At the end, you will have the returned user information from firebase in currentUser state object.
I hope that helps!

Using React and Redux Hooks, why is my action not firing?

Edit: SOLVED! Please see below.
I want my Blog component to fire the fetchBlog action creator every time the browser requests its URL, be it via a link or a refresh. I'd like to do it with the React useEffect Hook and with the React-Redux useDispatch and useSelector Hooks. However, my action only fires when following the link to the page; I do not understand why, even after reading several explanations (like the official docs).
Here is the code:
// Everything duly imported, or else VSC would yell at me
export default function Blog() {
const dispatch = useDispatch();
// slug is set here with useSelector, this always works
useEffect(() => {
dispatch(fetchBlog(slug))
}, [slug, dispatch]);
const blog = useSelector((state) => state.blogs[0]);
// return renders the blog information from the blog constant
// since the action does not fire, blog is undefined because state.blogs is an empty array
}
I know that, on refresh, fetchBlog does not fire because of Redux DevTools and also because I put a debugger there. (And the back-end logs don't show the request coming in.) The action creator itself and the reducer must be working; if they weren't, the page would not load correctly when visited through a link.
Edit: I have determined useSelector and useDispatch are not the root cause of the problem, as changing the code to use connect with mapStateToProps and mapDispatchToProps gives the same result. The issue seems to be with useEffect.
I think the problem is you are returning the call to dispatch. Functions returned from useEffect are clean up functions, so I don't think this would run on mount, or update - only before unmount. Try this:
export default function Blog() {
// ...
// Don't return from useEffect. Just call dispatch within the body.
useEffect(() => {
dispatch(fetchBlog(slug);
}, [slug, dispatch]);
// ...
}
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
I'd like to clarify what the issue was, which #Trace guided me to finding.
useEffect wasn't being called on refresh because it gets called after the component renders/returns. When refreshing, the state - including the blog data - is lost; instead of returning, a TypeError is thrown because data.title doesn't exist. So useEffect never gets the chance of being called and fetch the blog's content.
The solution to that goes like this:
export default function Blog() {
// ...
useEffect(/* ... */)
const blog = useSelector((state) => state.blogs[0]);
if (!blog) {
return <p>Loading...</p>
}
// return actual blog contents here
}
So now fetchBlog does get called, updating blog and rendering the content.
It isn't clear to me where the slug comes from.
In theory useEffect runs after every render. In case of multiple parameters it will run the callback when one of the array parameters passed in the second argument changes.
Either create a useEffect with empty array as second argument to run it 'once' (e.g. when you refresh) or check the slug value.
Edits after checking the repo:
It's not going to work because useEffect is run AFTER the render (which was included in my answer although someone downvoted it). Dispatching the call will only happen after, or not at all if the exception was thrown before (in this case a nullpointer).
You can get the slug from react-router with match, may be handy for you to know.
export default function Blog({ match }) {
const slug = match.params.slug;
etc
The git repo shows how dispatch as is added as array parameter to useEffect, which is not necessary.

React life cycle method for making API call when component is re-rendered

I have a component called <Header> where I have a log-in form that makes an API call to log a user in.
I have a function in my header component that makes some API calls to fetch some data and update the menu items in the header once successfully logged in:
componentDidMount() {
const { auth, actions, children } = this.props;;
if (auth.isLoggedIn) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
The problem I am having is that the very first time the user logs in the above componentDidMount function does not trigger because the header component was already mounted the first time the page was loaded.
I have tried to use componentDidUpdate and componentWillReceiveProps but they get triggered multiple times and I get request timeout errors.
Any ideas which lifecycle method I could use to achieve this?
Yes you are on the right path, you should use the componentWillReceiveProps lifecycle method. The trick to prevent infinite loops and constantly making requests, you have to perform a check to test whether the props you care about actually changed or not:
componentDidMount() {
this.fetchData(this.props);
}
componentWillReceiveProps(nextProps) {
if(nextProps.auth.isLoggedIn !== this.props.auth.isLoggedIn) {
this.fetchData(nextProps);
}
}
fetchData(props) {
const { auth, actions, children } = props;
if (auth.isLoggedIn) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
I can't tell you why it is called multiple times, but I can tell you that it should not matter. The problem is that you're not comparing the props for what has changed. If you do this, the code will behave the way you want:
componentWillReceiveProps(newProps) {
const { auth, actions, children } = newProps;
if (auth.isLoggedIn !== this.props.auth.isLogin) {
actions.getAssessmentRequest();
actions.getFirstSubsectionRequest();
}
}
See also the official ReactJS documentation, which states: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
Hope this helps.

Categories