Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.6k views
in Technique[技术] by (71.8m points)

react hooks - Unable to pass objects state and dispatch to provider when using createContext and useReducer with typescript

I am trying to have a behaviour similar to the redux' using contextApi and hooks, but I'm having some trouble with typescript.

I want to provide both the state of my application and the dispatch to be able to access and modify the store from within a component. The issue is that when I use createContext, I still don't have neither the state nor the dispatch objects at hand, because I can only call useReducer inside a React component. Therefore I was only able to call createContext declaring that the type of the argument is any , in order to pass null at that point and pass another dispatch and state later on.

    import React, { createContext, useReducer } from 'react';
    import reducerAuth, { initialState } from './reducerAuth';
    const ContextAuth = createContext<any>(null);
    const StateProvider = (
      { children }:{children:JSX.Element},
    ) => {
      const { Provider } = ContextAuth;
      const [state, dispatch] = useReducer(reducerAuth, initialState);
      return (<Provider value={{ state, dispatch }}>{children}</Provider>);
    };
    export { ContextAuth, StateProvider };

Is there a way which I can pass state and dispatch without having to declare type any and without having typescript conflicts?

I didn't want to use redux to achieve all of this, because I want to use native tools, but redux seemed easier, even though very similar.

I tyed to pass the type of the object {state,dispatch} to create context, the following way:

    const [state, dispatch] = useReducer(reducerAuth, initialState);
      type UseReducer = {
        state: typeof state,
        dispatch: typeof dispatch,
      }
      const ContextAuth = createContext<UseReducer | null>(null);

But in this case I am no longer able to export Context Auth, because it is then created inside the StateProvider function.

question from:https://stackoverflow.com/questions/65836693/unable-to-pass-objects-state-and-dispatch-to-provider-when-using-createcontext-a

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The root of your problem is that you are trying to infer the type of an object after it's been created rather than understanding what type of object will be created. In my opinion, this is a backwards design.

It is better to avoid using typeof when possible. There are things that you can make clear with a type or interface that typeof cannot possibly infer on its own. Can the string "myString" be any string value, or is it a string literal? Can this null property only ever be null, or could it be null or some other type? And what other type would that be? Hopefully you get the point.

dispatch has a known type. It is a function which takes an action and returns void. You do not need to use typeof dispatch to get its type.

type Dispatch<A> = (value: A) => void;

The generic variable A represents the acceptable action arguments. What you use depends on how strictly-typed you want your app to be. You could set it to a union of your specific action types or to a general interface that all of your actions will fulfill. Redux exports general types Action and AnyAction but React doesn't, so you have to define it yourself.

interface MyAction {
  type: string;
  payload: any;
}
type MyDispatch = Dispatch<MyAction>

Likewise, you should already know the expected type/interface for your State. If not, define it!

Here is a complete code example. The place to implement the types is on your initialState and reducerAuth. If those are typed properly, then StateProvider does not need any extra typing as the useReducer hook can infer the types of state and dispatch.

import React, { createContext, useReducer } from 'react';

interface MyState {
    // some actual data here
}

// initialState must fulfill the `MyState` interface
const initialState: MyState = {};

// just an example, you can define this type however you want
interface MyAction {
    type: string;
    payload: any;
}

// reducer declaration depends on the types for `MyState` and `MyAction`
const reducerAuth = (state: MyState, action: MyAction): MyState => {
    // actual reducer here
    return state;
};

// these are the values in your context object
interface MyContext {
    state: MyState;
    dispatch: React.Dispatch<MyAction>
}

const ContextAuth = createContext<MyContext | null>(null);

const StateProvider = ({ children }: { children: JSX.Element }) => {
    const { Provider } = ContextAuth;
    const [state, dispatch] = useReducer(reducerAuth, initialState);
    return (
        <Provider value={{ state, dispatch }}>
            {children}
        </Provider>
    );
};

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...