1

i am using ionic-react with below code and its giving error

Type '{ state: IState; dispatch: React.Dispatch; }' is not assignable to type 'IState'. Object literal may only specify known properties, and 'state' does not exist in type 'IState'.

The code looks like below

State.tsx

import React from "react";

export interface IState {
    count: number;
    loggedIn: boolean;
  }

// set the initial values
const initialState = { count: 0, loggedIn: false };

export type ActionType =
  | { type: "setLoggedIn"; payload: any }
  | { type: "error" };


// create the context
export const Context = React.createContext<IState>(initialState);

export const TheProvider = ({ children }: any): any => {
    /**
     * @param {*} state
     * @param {*} action
     */
    const reducer = (state: IState, action: ActionType): IState => {
      switch (action.type) {
        case "setLoggedIn":
          return { ...state, ...action.payload };
        default:
          throw new Error();
      }
    };
  
    const [state, dispatch] = React.useReducer(reducer, initialState);
  
    // wrap the application in the provider with the initialized context
    return (
      <Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
    );
  };
  
  export default Context;

Login.tsx

import AppContext, { TheProvider, IState }  from './State'; 


...

const Login: React.FC = () => {
    const { state, dispatch } = React.useContext<any>(AppContext);
  

    const doLogin = async () => {

        try{
          
           dispatch({
                type: 'setLoggedIn',
                payload: {loggedIn: false}
            })

        }catch(err){
          console.error("failed to login with erro", err)
        }
      };

    return (
 <TheProvider>
        <form className="ion-padding">
        <IonToolbar>
          <IonTitle>Login</IonTitle>
        </IonToolbar>
          <IonItem style={{paddingTop:'100px'}}>
            <IonLabel position="floating">Email</IonLabel>
            <IonInput type="email" value={email} onIonChange={e => setEmail(e.detail.value!)}/>
          </IonItem>
          <IonItem>
            <IonLabel position="floating">Password</IonLabel>
            <IonInput type="password" value={password} onIonChange={e => setPassword(e.detail.value!)}/>
          </IonItem>
          <IonItem>
            <IonLabel>{errMessage}</IonLabel>
          </IonItem>
          
          <IonButton className="ion-margin-top" onClick={doLogin} expand="block">
            <IonIcon slot="start" icon={mailOutline} />Login
          </IonButton>
        </form>
        </TheProvider>
    )
};

i am now getting error on dispatch as

TypeError: dispatch is not a function at doLogin

Moblize IT
  • 1,140
  • 2
  • 18
  • 44
  • curious why you feel the need to use context AND use reducer? What are you gaining by adding that additional complexity? just add the login function to the context and call it directly... – Aaron Saunders Feb 10 '21 at 20:04
  • well may be its complex for this specific usecase, but i anyhow need state management at my app level. so this was just a way to start using and putting needed pieces together – Moblize IT Feb 10 '21 at 22:17
  • 1
    ok - https://codesandbox.io/s/ionic-tabs-context-simple-g1r2t – Aaron Saunders Feb 11 '21 at 02:32
  • @AaronSaunders this code does not work with ionic-react. – Moblize IT Feb 11 '21 at 03:19

1 Answers1

1

The issue is in the discrepancy between these two lines:

export const Context = React.createContext<IState>(initialState);
// ...
<Context.Provider value={{ state, dispatch }}>

If you want your context to include the state as well as the dispatch, you need to change your initial Context object to something like this:

interface ContextType {
    state: IState,
    dispatch?: React.Dispatch<ActionType>
  }
export const Context = React.createContext<ContextType>({ state: initialState });

In your call to useContext, you can remove the generic <any>. Then you may need to check that dispatch is defined, since it is set in ContextType above as optional:

import AppContext, { TheProvider }  from './State'; 

// ...

const Login = () => {
  const { state, dispatch } = React.useContext(AppContext);

  const doLogin = async () => {
    if (dispatch === undefined) {
      return; // or display error message
    }
    try {
      dispatch({
        type: "setLoggedIn",
        payload: { loggedIn: false },
      });
    } catch (err) {
      console.error("failed to login with error:", err);
    }
  };

  return <div id="login"></div>; // presumably this holds your inputs, 
                                 // submit button, forgot password link, etc.
}

Then, to make sure the context is properly passed down, you have to make sure Login is a child of TheProvider. In the same Login.tsx file, you could define a new component LoginPage like so, if you have a Layout component that includes your header, footer, CSS, etc:

const LoginPage = () => {
  return <TheProvider>
           <Layout>
             <h2>Please enter your information below to log in:</h2>
             <Login />
           </Layout>
         </TheProvider>
}
jdaz
  • 5,964
  • 2
  • 22
  • 34
  • i had to update my question as this now breaks the usage part where the above code is in the State.tsx and trying to use it in Login.tsx – Moblize IT Feb 10 '21 at 07:13
  • can you please advise further? – Moblize IT Feb 11 '21 at 03:20
  • at the dispatch call it shows me an error: const dispatch: React.Dispatch | undefined Cannot invoke an object which is possibly 'undefined'. – Moblize IT Feb 12 '21 at 00:49
  • Interesting, I don't get that error, but I can see why you're getting it. Updated again, I changed a couple of things around. – jdaz Feb 12 '21 at 01:45
  • Well, this just short circuits the dispatch actual call. dispatch is undefined and hence the code returns right there without doing actual dispatch. – Moblize IT Feb 12 '21 at 03:53
  • It won't be undefined as long as you wrap your `Login` component inside the `TheProvider` component, which I assumed you were doing. Added more code for clarification. – jdaz Feb 12 '21 at 05:00
  • i was not wrapping but just now wrapped it and updated in my original question as well for you to take a look. however, this did not help me and dispatch is still undefined. I have wrapped my Tab1.tsx code as well which is also using dispatch. – Moblize IT Feb 12 '21 at 05:51
  • can u pl further advise as it still does not work – Moblize IT Feb 17 '21 at 05:07
  • Look at my answer closely, the provider needs to be outside of the login component itself – jdaz Feb 17 '21 at 14:45