Home > Mobile >  State changed in context provider not saved
State changed in context provider not saved

Time:12-31

So I'm trying to centralize some alert-related logic in my app in a single .tsx file, that needs to be available in many components (specfically, an "add alert" fuction that will be called from many components). To this end I am trying to use react context to make the alert logic available, with the state (an array of active alerts) stored in App.tsx.

Alerts.tsx


export interface AlertContext {
    alerts: Array<AppAlert>,
    addAlert: (msg: React.ReactNode, style: string, callback?: (id: string) => {}) => void,
    clearAlert: (id: string) => void
}
[...]
export function AlertsProvider(props: AlertsProps) {

    function clearAlert(id: string){
        let timeout = props.currentAlerts.find(t => t.id === id)?.timeout;
        if(timeout){
            clearTimeout(timeout);
        }

        let newCurrent = props.currentAlerts.filter(t => t.id != id);
        props.setCurrentAlerts(newCurrent);
    }

    function addAlert(msg: JSX.Element, style: string, callback: (id: string) => {}) {
        console.log("add alert triggered");
        let id = uuidv4();
        let newTimeout = setTimeout(clearAlert, timeoutMilliseconds, id);
        let newAlert = {
            id: id,
            msg: msg,
            style: style,
            callback: callback,
            timeout: newTimeout
        } as AppAlert;
        let test = [...props.currentAlerts, newAlert];
        console.log(test);
        props.setCurrentAlerts(test);
        console.log("current alerts", props.currentAlerts);
    }

    let test = {
        alerts: props.currentAlerts,
        addAlert: addAlert,
        clearAlert: clearAlert
    } as AlertContext;

    return (<AlertsContext.Provider value={test}>
        { props.children }
    </AlertsContext.Provider>);
}

App.tsx

function App(props: AppProps){
[...]
    const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
    const alertsContext = useContext(AlertsContext);

    console.log("render app", alertsContext.alerts);
    return (
      <AlertsProvider currentAlerts={currentAlerts} setCurrentAlerts={setCurrentAlerts}>
          <div className={ "app-container "   (error !== undefined ? "err" : "") } >
              { selectedMode === "Current" &&
                <CurrentItems {...currentItemsProps} />
              }
              { selectedMode === "History" &&
                <History {...historyProps } />
              }
              { selectedMode === "Configure" &&
                <Configure {...globalProps} />
              }
          </div>
          <div className="footer-container">
              {
                alertsContext.alerts.map(a => (
                  <Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
                    {a.msg}
                  </Alert>
                ))
              }
              {/*<Alert variant="danger" dismissible transition={false}
                  show={ error !== undefined }
                  onClose={ dismissErrorAlert }>
                  <span>{ error?.msg }</span>
            </Alert>*/}
          </div>
      </AlertsProvider>
    );
}

export default App;

I'm calling alertsContext.addAlert in only one place in CurrentItems.tsx so far. I've also added in some console statements for easier debugging. The output in the console is as follows:

render app Array [] App.tsx:116
XHRGEThttp://localhost:49153/currentitems?view=Error [HTTP/1.1 500 Internal Server Error 1ms]
Error 500 fetching current items for view Error: Internal Server Error CurrentItems.tsx:94
add alert triggered Alerts.tsx:42
Array [ {…}, {…} ] Alerts.tsx:53
current alerts Array [ {…} ] Alerts.tsx:55
render app Array []

So I can see that by the end of the addAlert function the currentAlerts property appears to have been updated, but then subsequent console statement in the App.tsx shows it as empty. I'm relatively new to React, so I'm probably having some misunderstanding of how state is meant to be used / function, but I've been poking at this on and off for most of a day with no success, so I'm hoping someone can set me straight.

CodePudding user response:

const alertsContext = useContext(AlertsContext);

This line in App is going to look for a provider higher up the component tree. There's a provider inside of App, but that doesn't matter. Since there's no provider higher in the component tree, App is getting the default value, which never changes.

You will either need to invert the order of your components, so the provider is higher than the component that's trying to map over the value, or since the state variable is already in App you could just use that directly and delete the call to useContext:

function App(props: AppProps){
[...]
    const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
    // Delete this line
    // const alertsContext = useContext(AlertsContext);

    console.log("render app", currentAlerts);
[...]
    {
      currentAlerts.map(a => (
        <Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
          {a.msg}
        </Alert>
      ))
    }
}
  • Related