Reputation: 81
I am trying to get a component to re-render by changing it's state, but it isn't working. I've tried several methods based on researching this, but none work.
The app allows a user to see all the data, but not change anything, unless they log in. The DevNav component has a link that is either 'Login', if the the user is not logged in, or 'Logout' if the user is logged in. The login status is held both in local storage, and global context. This is the DevNav code:
import React, { useState, useContext, useEffect } from 'react';
import { Menu } from 'semantic-ui-react';
import DevDataContext from '../../contexts/DevDataContext'
import SetupContext from '../../contexts/SetupContext';
import "./style.css";
const DevNav = () => {
let [, setState] = useState()
const devCtx = useContext(DevDataContext)
const setupCtx = useContext(SetupContext)
const isLoggedIn = localStorage.getItem("jtsy-login");
useEffect(() => {
console.log('DEVNAV useEffect isLoggedIn', isLoggedIn)
setState({})
}, [isLoggedIn])
const openLoginModal = () => {
setupCtx.updateLoggedIn()
setupCtx.openLoginModal(true)
}
const openLogoutModal = () => {
setupCtx.updateLoggedIn()
setupCtx.openLogoutModal(true)
}
let content = (
<div>
<Menu inverted stackable fixed="top" className="menu">
<Menu.Menu position="left">
<Menu.Item as="a" href="/" name="home">
</Menu.Item>
</Menu.Menu>
(... more links)
{!setupCtx.state.loggedIn ? (
<Menu.Item name="login" onClick={openLoginModal}>
</Menu.Item>
) : (
<Menu.Item name="logout" onClick={openLogoutModal}>
</Menu.Item>
)
}
</Menu.Menu>
</Menu>
</div >
)
return content
}
export default DevNav;
The variable 'loggedIn' in SetupContext is true or false, based on whether the user is loggin in or out. Clicking on the Login/Logout link executes setCtx.updateLoggedIn(), which toggles the value in context, and opens the appropriate modal. The code to change the local storage value 'jtsy-login' is in each modal component, which is assigned to 'isLoggedIn'.
I'm using useEffect to update the component state, running it when 'isLoggedIn' changes. This appears to be working, as shown in this screen capture:
I was logged in, as shown on the first line in the console, so the 'Logout' link is shown. But after I logged out, the link didn't change, even though useEffect ran a second time and the component state was changed, as shown by the second line in the console. The link changes only on a screen refresh.
Why doesn't this work?
Upvotes: 0
Views: 565
Reputation: 81
The first 2 comments lead to working code, although this is likely not the best solution. I changed DevNav so the condition to choose which link (Login or Logout) to display is based on 'isLoggedIn', assigned from the local storage value, instead of setCtx.state.loggedIn.
{isLoggedIn === 'false' ? (
<Menu.Item name="login" onClick={openLoginModal}>
</Menu.Item>
) : (
<Menu.Item name="logout" onClick={openLogoutModal}>
</Menu.Item>
)
Now, the component re-renders. I think this is due to the inherent delay in using context versus retrieving from local storage - the value of setupCtx.state.loggedIn hadn't changed before the component was executed after closing the modal.
However, as pointed out, accessing local storage is a side effect that should be inside of useEffect(). I haven't figured out how to do that yet.
Upvotes: 0
Reputation: 362
The problem is that :
when you get an item from the localStorage, you will always get a string.
So the condition !setupCtx.state.loggedIn
will never change since the output is always a string.
In fact, if you try to do this : console.log("false" === true)
the output will be true
because it's a filled string.
Solution :
You have to JSON.parse()
the collected info in the localstorage before to execute any condition on it.
Here is the documentation about JSON.parse : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/JSON/parse
Upvotes: 2