ansh sachdeva
ansh sachdeva

Reputation: 1179

How to update react context from multiple conmponents?

So i am trying to lear about React contexts and I am a bit confused. from its documentation:

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

So this means i can have the whole state of an app as global and i can update it from any child components , right? However I am confused about how to go with it. I have a small app that shows either login, signup or Logged in screen to user based on their input. i am expecting that any of the following components should be able to change the value of global object stored in context, but i am unsure about how to go with it (mentioned the unsure functions as todos )

// context
const MyAppSettings = React.createContext(
    {
        userId:null,
        enableMarketing:false,
        theme:"light"
    }
)
//ui components(having access to local state as well as global context

function SettingsUI({onThemeChange,onConsentChange}){
    let settings = useContext(MyAppSettings)

    return(
        <div>
            <button onClick={e=>onThemeChange()}>Change Theme to {settings.theme==="light"?"dark":"light"}</button>
            <br/>
            <button onClick={e=>onConsentChange()}> {settings.enableMarketing?"withdraw consent for marketing emails":"give consent for marketing emails"}</button>
        </div>
    )


}
function Auth({onAuthClick}){
    let settings = useContext(MyAppSettings)
    let textColor = settings.theme==="light" ? "black" : "white"
    let bg = settings.theme==="light"?"white": "brown"
    let finalStyling= {border:"1px solid black",width:"200px",display:"block",marginBottom:"4px",backgroundColor:bg,color:textColor}

    let [currentEmail,updateEmail] = useState("")
    let emailUI =  <input type="text" style={finalStyling} placeholder="email" value={currentEmail} onChange={e=>updateEmail(e.target.value)} />

    let [currentPwd,updatePwd] = useState("")
    let passwordUi =  <input type="password" style={finalStyling} placeholder="password" value={currentPwd} onChange={e=>updatePwd(e.target.value)}  />

    let [currentName,updateName] = useState("")
    let [isSignup,toggleSignUp ]= useState(false)
    let nameUi =  isSignup ? <input type="text" style={finalStyling} placeholder="name"  value={currentName} onChange={e=>updateName(e.target.value)} /> :  ""

    let authBtnText = isSignup? "Sign up now!" : "Login now!"
    let switchBtnText = isSignup? "Login Instead" : "Signup Instead"

    function getCurrentInfo(){
        return {
            email:currentEmail,
            pwd:currentPwd,
            isUserSigningUp:isSignup,
            name:currentName
        }
    }

    return(
        <>
            {nameUi}
            {emailUI}
            {passwordUi}
            <div>
                <button onClick={e=>onAuthClick(getCurrentInfo())} >{authBtnText}</button>
                <button onClick={e=>toggleSignUp(!isSignup)} >{switchBtnText}</button>
            </div>
        </>
    )
}
function LoggedIn({logoutClick}){
    let settings = useContext(MyAppSettings)
    let textColor = settings.theme === "light" ? "black" : "white"
    let bg = settings.theme === "light" ? "white" : "brown"
    return (
        <div style={{padding: "8px", backgroundColor: bg, color: textColor}}>
            <p>You have successfully logged in. your user id is {settings.userId}</p>
            <button onClick={e => logoutClick()}>logout</button>
        </div>
    )
}

//component controlling  the other components and responsible for changing context values (TODO: HOW??)

function UserLoginSystem(){
    let settings = useContext(MyAppSettings)

    let onThemeChangeListener = ()=> {/*todo toggle theme settings to dark/light*/}
    let onConsentChangeListener = ()=> {/*todo toggle theme consent settings*/}

    let section1 = <SettingsUI onConsentChange={onConsentChangeListener} onThemeChange={onThemeChangeListener}/>


    let onUserTryingToAuthenticate = (credsRequest)=>{/*todo set user to uuid if email/password match some static email pwd*/}
    let section2Auth= <Auth onAuthClick={onUserTryingToAuthenticate}/>

    let onUserTryingToLogout = ()=>{/*todo set user to null*/}

    let section2LoggedIn = <LoggedIn logoutClick={onUserTryingToLogout}/>

    return (
        <div style={{width: "max-content", padding: "8px", border: "1px solid purple"}}>
            <h1>using context apis</h1>
            <div style={{width: "max-content", padding: "8px", border: "1px solid purple"}}>
                {settings.userId!==null && section1}
                {settings.userId === null ? section2Auth : section2LoggedIn}
            </div>
        </div>
    )


}


// rendering user login system
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode>  <UserLoginSystem/> </React.StrictMode>);

Screenshots:

login signup logged in

Upvotes: 1

Views: 6142

Answers (1)

Drew Reese
Drew Reese

Reputation: 202872

You appear to be missing the MyAppSettings context Provider component that provides the context value to consumers.

Also missing from the MyAppSettings Context itself are the state updater functions to update the state the context holds.

Example

MyAppSettingsProvider

Create a context with default value showing the shape of the Context value

export const MyAppSettings = React.createContext({
  userId: null,
  setUserId: () => {},
  enableMarketing: false,
  toggleConsent: () => {},
  theme: "light",
  toggleTheme: () => {},
});

Create the context Provider and declare the state and state updater functions

const MyAppSettingsProvider = ({ children }) => {
  const [userId, setUserId] = useState(null);
  const [enableMarketing, setEnableMarketing] = useState(false);
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => setTheme(theme => theme === 'light' ? 'dark' : 'light');
  const toggleConsent = () => setEnableMarketing(enabled => !enabled);

  const value = {
    userId,
    setUserId,
    enableMarketing,
    toggleConsent,
    theme,
    toggleTheme,
  };

  return (
    <MyAppSettings.Provider value={value}>
      {children}
    </MyAppSettings>
  );
};

export default MyAppSettingsProvider;

Oftentimes a custom hook will be created for convenience

export const useMyAppSettings = () => useContext(MyAppSettings);

UserLoginSystem

This component needs to be wrapped in the MyAppSettingsProvider so it and any of its descendants can consume the MyAppSettings context value.

// rendering user login system
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <MyAppSettingsProvider>
      <UserLoginSystem/>
    </MyAppSettingsProvider>
  </React.StrictMode>
);

Consuming components

The consuming component import and use the useMyAppSettings and destructure the context values they need to reference and/or update.

SettingsUI

function SettingsUI({ onConsentChange }) {
  const {
    enableMarketing,
    theme,
    toggleConsent,
    toggleTheme
  } = useMyAppSettings();

  return(
    <div>
      <button onClick={toggleTheme}>
        Change Theme to {theme === "light" ? "dark" : "light"}
      </button>
      <br/>
      <button onClick={toggleConsent}>
        {enableMarketing
          ? "withdraw consent for marketing emails"
          : "give consent for marketing emails"
        }
      </button>
    </div>
  );
}

Auth

function Auth({ onAuthClick }){
  const { theme } = useMyAppSettings();

  const color = theme === "light" ? "black" : "white";
  const backgroundColor = theme === "light" ? "white": "brown";

  const finalStyling = {
    border: "1px solid black",
    width: "200px",
    display: "block",
    marginBottom: "4px",
    backgroundColor,
    color,
  };

  ...

  return(
    ...
  );
}

Login

function LoggedIn({ logoutClick }){
  const { theme } = useMyAppSettings();

  const color = theme === "light" ? "black" : "white";
  const backgroundColor = theme === "light" ? "white": "brown";

  return (
    <div style={{ padding: "8px", backgroundColor, color }}>
      <p>You have successfully logged in. your user id is {settings.userId}</p>
      <button onClick={logoutClick}>logout</button>
    </div>
  );
}

Upvotes: 6

Related Questions