2

I am having a problem which needs your helps

I have a changeProfile function which will be executed after onClick in /editpage

const appContext = useContext(AppContext);
const [userLogin, setUserLogin] = appContext.userLogin;
const [userProfile, setUserProfile] = useState<UserProfile>({
    name: userLogin.name,
    ....
});
const changeProfile = e => {
    e.preventDefault();
    post(API)
        .then(() => {
            setUserLogin({
                ...userLogin,
                name: userProfile.name,
            });
            router.push('/mypage');
        })
        .catch(error => {
            setEditError(error[0]);
            window.scrollTo(0, 0);
        });
};

context.tsx

const [userLogin, setUserLogin] = useState({
     email: '',
     name: '',
     ....
});

after page redirects to /mypage, console logs warning error as title and it only logs warning at the first time (if I back to /editpage and changeProfile one more time, console logs nothing in/mypage)

I have tried to redirect after setUserLogin done as code below but it's not working

const changeProfile = e => {
    e.preventDefault();
    post(API)
        .then(() => {
            setUserLogin({
                ...userLogin,
                name: userProfile.name,
            });
        })
        .catch(error => {
            setEditError(error[0]);
            window.scrollTo(0, 0);
        });
};

const firstUpdate = useRef(true);
useLayoutEffect(() => {
    if (firstUpdate.current) {
        firstUpdate.current = false;

        return;
    }
    console.log(userLogin); //already updated
    router.push('/mypage');
}, [userLogin]);

Thanks for reading!

PS: Problem can be solved with

setTimeout(() => {
    router.push('/mypage');
}, 1000);

but its absolutely not a right choice

iamhuynq
  • 5,357
  • 1
  • 13
  • 36

1 Answers1

1

As the warning suggests, because your API code is asynchronous, if you redirect away from the page too fast, the setState may occur after the component has already been unmounted. What you can do, is use the callback argument of setState (assuming you're calling setState somewhere in the setUserLogin function), to redirect after the state update has already been completed.

Edit: You could try adding another state variable to your context to signify that an update has been performed, something like updated: false and then with your setUserLogin call:

setUserLogin({
    ...userLogin,
    name: userProfile.name,
    updated: true
});

Then you can use the useEffect hook to check for this condition and then redirect:

useEffect(() => {
    if (userLogin.updated) {
        router.push('/mypage');
    }
})

Then at some point, you would have to reset this variable to false. However, now that I know your setUserLogin is coming from the AppContext component, I think the issue is that this component is getting unmounted when it shouldn't be. Make sure you're rendering this component high enough in the component tree so that the mypage you redirect to still has this AppContext mounted

Edit 2: A good approach to prevent this error is by adding a guard to a component class, which keeps track of whether the component is mounted or not. Something like this:

class AppContext extends Component {
    _mounted = False;

    componentDidMount() {
        this._mounted = true;
    }

    componentWillUnmount() {
        this._mounted = false;
    }

    ...
}

Then, when you call setState or the update function from useState, you can first check to see if the component is mounted or not before trying to update:

if (AppContext._mounted) {
    setUserLogin({
        ...userLogin,
        name: userProfile.name,
        updated: true
    });
}

However, in your case, this may prevent the information from being updated as you expect, since the component is unmounting, which is why I think the issue lies with why the component is unmounting

awarrier99
  • 3,628
  • 1
  • 12
  • 19
  • thanks, I updated question and you can see my `setUserLogin` – iamhuynq Apr 08 '20 at 04:05
  • ahh no, you can see I updated state in `context.tsx` and info `userLogin` be used in `/mypage` and another pages – iamhuynq Apr 08 '20 at 04:14
  • @iamhuynq yup I realized that after looking at your context code, that's why I removed the comment – awarrier99 Apr 08 '20 at 04:15
  • @iamhuynq also, where is the `useLayoutEffect` function coming from? – awarrier99 Apr 08 '20 at 04:16
  • it from `import { useLayoutEffect } from 'React'`, you can see here [Make React useEffect hook not run on initial render](https://stackoverflow.com/questions/53253940/make-react-useeffect-hook-not-run-on-initial-render/53254028#53254028) – iamhuynq Apr 08 '20 at 04:20
  • thank you, but it seem not working because I did `console.log(userLogin)` inside `useLayoutEffect`, I can see `userLogin` already updated by `setUserLogin` – iamhuynq Apr 08 '20 at 04:44
  • @iamhuynq hmm ok can you also include the code which shows where this component and the `AppContext` component are rendered? As I mentioned in the last part of my answer, an unintentional unmounting of the component could be causing this – awarrier99 Apr 08 '20 at 04:48
  • @iamhuynq I also added an edit which will prevent this warning, but it may also prevent the information from being updated as you expect – awarrier99 Apr 08 '20 at 04:57