typescriptlocal-storagehookuse-contextcreatecontext

Updating Parent Component using useContext


I'm trying to change the state of a parent component from a child component. My Component structure is basically like this.

<Admin>
    <Nav/> //Nav Bar
    {changePass && props.children} //Change Password Form
</Admin>

My Nav, in turn has several child components:

  <NavBar>

    <NavItem menu="☰">
      <DropdownMenu/>
    </NavItem>
    
  </NavBar>

In the DropdownMenu component there are child button Components, Logout, Delete Account, and Change Password. The latter is from which I want to toggle the Change Password Component in Admin.tsx. In DropdownItem.tsx I have a bit of code to detect the button click:

//ChangePassContext.tsx
import { createContext } from "react";

export let ChangePassContext = createContext<boolean>(false)

 //DropdownItem.tsx
import { ChangePassContext } from "../Context/ChangePasswordContext/ChangePassContext";

 let changePass = useContext(ChangePassContext)

 //When window loads, get previous state from local storage
 if (destination === CHANGEPSSWD){
      if (localStorage.getItem('changePass') === "true"){
        changePass = !changePass
      } 
    }

    const handleMenu = (event: MouseEvent) => {
      event.preventDefault();
      if (destination === CHANGEPSSWD) {
   
        // Open Change Password Form
        if (changePass === false){
          localStorage.setItem('changePass', "true")
        } else {
          localStorage.removeItem('changePass')
        }
          changePass = !changePass
          console.log(changePass)
          window.location.reload() //I'll reference this in a moment
       } 
    }

I then call changePass in my Admin.tsx file and update it using what I have stored in my local storage:

let changePass = useContext(ChangePassContext)
if (localStorage.getItem("changePass") === "true"){
  changePass = !changePass
}

And as shown above, the changePass context controls whether the ChangePassword Form is opened or closed:

{changePass && props.children} //Change Password Form

The only problem is I can't get the state to update from my DropdownItem component to my Admin parent component. The only way I can get it to update is if I reload the page, as I do in the DropdownItem component, resulting in a very poor user Experience:

enter image description here

Ironically, as you can see, I have a working menu component (passing down props to children seems easier), and I did it with the useContext provider and useState. The problem is that when I try to set the context with useState, the context won't update, and I don't know why. Originally I tried setting my DropdownItem function to something simple like this:

  if (destination === CHANGEPSSWD) {
    // Open Change Password Form
    setChangePass(!changePass)
  } 

I also don't want to use localStorage at all but I can't seem to get useContext to work without it.

I'm very frustrated, especially with how ugly this code is. I hope someone can help.


Solution

  • Make your Admin file like this:

    //Admin.tsx
    import { createContext } from "react";
    export const ChangePassContext = createContext();
    
    function App() {
     const[changePass, setChangePass] = useState({});
    
     return (
      <ChangePassContext.Provider value={[changePass, setChangePass]}>
      <Admin>
        <Nav/> //Nav Bar
        {changePass && props.children} //Change Password Form
      </Admin>
      </UserContext.Provider>
      );
     }
    
    export default App;
    

    Then you can use it in your DropdownItem.tsx

    //DropdownItem.tsx
    import { useContext } from 'react';
    import { ChangePassContext } from '../../App';
    const DropdownItem = () => {
    const[changePass, setChangePass]= useContext(ChangePassContext);
    if (destination === CHANGEPSSWD) {
      // Open Change Password Form
      setChangePass(!changePass)
    }
    return (
       //your code
      );
    };