Why my component doesn’t re-render itself even tho I used useEffect?(React.js)

recently, I am making my web application with a react and I’m struggling with a small issue(not small for me)

Below is my app.js and I want to conditional render NAV component by login/out status. (as you can see it is outside Switch!)

<>

      <Nav indexStatus={indexStatus} setIndexStatus={setIndexStatus} />
      
      <Index indexStatus={indexStatus} setIndexStatus={setIndexStatus} />

      <Switch>
        <Route exact path="/" component={Auth(LandingPage,null)}/>
        <Route exact path="/login" component={Auth(LoginPage,false)}/>
        <Route exact path="/calendar" component={Auth(Calendar,true)} />
        <Route exact path="/about" component={Auth(About,false)} />
        <Route exact path="/register" component={Auth(RegisterPage,false)}/>
      </Switch>
      
    </>

to check login status I gave true/false value to localStorage when user login/out

login:

dispatch(loginUser(body))
    .then(response=>{
      if(response.payload.loginSuccess){
        localStorage.setItem('user',response.payload.loginSuccess)
        props.history.push("/")
       console.log(props.parentNode)
       
       
      }else{
        alert('login failed')
      }
    })

logout:

 axios.get("/api/users/logout")
    .then(res=>{
      if(res.data.success){
        console.log(res)
       history.push('/login')
       localStorage.setItem('user','false')

        
    }else{
        alert("Logout failed")

    }
  })
}

and I gave localStorage value to state like below:

const [loginStatus,setLoginStatus] =useState(localStorage.getItem('user'))

I check it with console.log and it works fine

so I used useEffect to chanege loginStatus value, and re-render only localstorage has changed

const checkLoginStatus= localStorage.user
  useEffect(()=>{
    
    if(checkLoginStatus==='true'){
      setLoginStatus(true)
    }else{
      setLoginStatus(false)
    }
  },[checkLoginStatus])
  console.log(localStorage.user)

Finally, I wrote my code with ?: operator like below

return (
    <>
    {loginStatus ===true ? (
      <div>
      <div className="nav">
        <h2>TITLE</h2>
        <div className="buttons">
          <div>About</div>
          <div>How to use</div>
          <button onClick={logoutHandler}>Logout</button>

        <StyledLink to="/login"><FontAwesomeIcon icon={faSignInAlt}/></StyledLink>
        
        
          <button onClick={()=>setIndexStatus(!indexStatus)} >Index</button>
        </div>
      </div>
    </div>)

    
    :
     ( <div>
      <div className="nav">
        <h2>TITLE</h2>
        <div className="buttons">
          <div>About</div>
          <div>How to use</div>
          <button onClick={logoutHandler}>Logout</button>

        <StyledLink to="/login"><FontAwesomeIcon icon={faSignInAlt}/></StyledLink>
        <StyledLink to="/register"><FontAwesomeIcon icon={faUserPlus}/></StyledLink>
        
          <button onClick={()=>setIndexStatus(!indexStatus)} >Index</button>
        </div>
      </div>
    </div>
    )}
    </>

    
    
 
  
   
   );
     }

export default Nav

However, the problem is it only works partly, I have to refresh pages to work component conditional rendering. I’m not sure why is it like this even tho I used useEffect with second paramater [checkLoginStatus]

Can you tell me why is not work properly? and how my logic is wrong? Thx for reading, your help will be appreciated

Answer

You will want to lift the loginStatus state up to the App parent component so the state and updater function can be passed down to children components to read and/or update.

App

Move the loginStatus state to this parent component. Pass the loginStatus to the Nav component for it to handle its conditional rendering, and pass setLoginStatus to the Login component for it to update the state. Notice that the routes were reordered so they no longer need to specify the exact prop everywhere, they aren’t wrapped with the Auth HOC as this likely creates a new component each render cycle, and now the Login component is rendered on the render prop so we can slip in additional props.

const [loginStatus, setLoginStatus] = useState(
  () => !!JSON.parse(localStorage.getItem("user")),
);

return (
  <>
    <Nav
      indexStatus={indexStatus}
      loginStatus={loginStatus} // <-- pass login status
      setIndexStatus={setIndexStatus}
      setLoginStatus={setLoginStatus} // <-- pass updater
    />
  
    <Index indexStatus={indexStatus} setIndexStatus={setIndexStatus} />

    <Switch>
      <Route
        path="/login"
        render={props => <LoginPage {...props} setLoginStatus={setLoginStatus} />}
      />
      <Route path="/calendar" component={Calendar} />
      <Route path="/about" component={About} />
      <Route path="/register" component={RegisterPage} />
      <Route path="/" component={LandingPage} />
    </Switch>
  </>
);

Nav

Remove the loginStatus state and use the passed loginStatus value and setLoginStatus function to update the state in the parent.

function Nav({ indexStatus, loginStatus, setLoginStatus, setIndexStatus }) {
  const history = useHistory();

  const logoutHandler = () => {
    ...
    setLoginStatus(false); // <-- logged out
  };

  return loginStatus ? (
    ...
  )
  : (
    ...
  );
}

LoginPage

Access the setLoginStatus state updater function from props and set when user logs in successfully. Decorate the export with the Auth HOC (this should be done for all the components needing it).

function LoginPage(props) {
  ...

  const onSubmitHandler=(e)=>{
    e.preventDefault();

    let body={
      email: Email,
      password: Password
    };

    dispatch(loginUser(body))
      .then(response=>{
        if(response.payload.loginSuccess){
          localStorage.setItem('user',response.payload.loginSuccess)
          props.setLoginStatus(true); // <-- set logged in
          props.history.push("/");
        } else {
          alert('login failed');
        }
      });
  }

  return (
    <>
      ...
    </>
  );
}

export default Auth(withRouter(LoginPage));

Update

The reason your components that aren’t receiving passed props is because the Auth HOC isn’t passing them through.

export default function(SpecificComponent, option, adminRoute = null) {
  ...
  function AuthenticationCheck(props) { // <-- props
    ...
    return (
      <SpecificComponent /> // <-- not passed through
    );
  }
  return AuthenticationCheck
}

Should be

export default function(SpecificComponent, option, adminRoute = null) {
  ...
  function AuthenticationCheck(props) { // <-- props
    ...
    return (
      <SpecificComponent {...props} /> // <-- passed through
    );
  }
  return AuthenticationCheck
}