reactjsflowtype

Typing React Context with Flow


We're using Flow in our project and I'm chasing my tail on this one. Am using React Context to store a user's favourite assets, this will be used in multiple places. I can't get Flow happy with React Context whose value is an object as follows:

// this is the problem line!! currently having to $FlowIgnore it
export const FavoriteAssetsContext = React.createContext(undefined);

// this is all fine
export function FavoriteAssetsProvider({
  children,
}: FavoriteAssetsProviderProps): React.Node {
  const [favoriteAssets, setFavoriteAssets] = useState([]);
  const [favoriteAssetIds, setFavoriteAssetIds] = useState([]);

  const _isFavorite = (asset: Asset): boolean => { // private func };

  const addFavorite = useCallback((asset: Asset): void => {
    //  exported function - performs setState, no return value
  });

  const removeFavorite = useCallback((asset: Asset): void => {
    //  exported function - performs setState, no return value
  });

  return (
    <FavoriteAssetsContext.Provider
      value={{ favoriteAssets, favoriteAssetIds, addFavorite, removeFavorite }}
    >
      {children}
    </FavoriteAssetsContext.Provider>
  );
}

So the consumers of the context can access four properties on the value object. Flow is complaining about the lack of typing of the FavoriteAssetsContext which we export.

Per the Flow docs I've tried this:

type FavoriteAssetsContextType = {
    favoriteAssets: Array<Asset>,
    favoriteAssetIds: Array<string>,
    addFavorite: (asset: Asset) => void,
    removeFavorite: (asset: Asset) => void,
};

export const FavoriteAssetsContext = React.createContext<FavoriteAssetsContextType>({});

but I get the following error:

Cannot build a typed interface for this module. You should annotate the exports of this module with types. Cannot determine the type of this call expression. Please provide an annotation, e.g., by adding a type cast around this expression. [signature-verification-failure]

I have tried several permutations and combinations of type casting per Flow docs, which all look horribly wrong. It's complaining about the right hand side so I tried the following syntax:

// without generic - shouldn't be needed right?
export const FavoriteAssetsContext = (React.createContext({}) : FavoriteAssetsContextType);
// and with generic - ???
export const FavoriteAssetsContext = (React.createContext<FavoriteAssetsContextType>({}) : FavoriteAssetsContextType);

but both times I get

Cannot cast React.createContext<...>(...) to FavoriteAssetsContextType because property removeFavorite is missing in React.Context [1] but exists in FavoriteAssetsContextType [2]. [prop-missing]

The docs have simple examples whereby the value is a simple string and an extensive search hasn't come up with anything examples where value is an object. Any help much appreciated!


Solution

  • type FavoriteAssetsContextType = {
        favoriteAssets: Array<Asset>,
        favoriteAssetIds: Array<string>,
        addFavorite: (asset: Asset) => void,
        removeFavorite: (asset: Asset) => void,
    };
    
    export const FavoriteAssetsContext = React.createContext<FavoriteAssetsContextType>({});
    

    This one solution is in the right direction however currently content is initialised with the empty object ({}), but in the type that is provided in the FavoriteAssetsContextType there are expected values for all the keys in it.

    So the solution can be to update FavoriteAssetsContextType making all keys optional:

    type FavoriteAssetsContextType = {
        favoriteAssets?: Array<Asset>,
        favoriteAssetIds?: Array<string>,
        addFavorite?: (asset: Asset) => void,
        removeFavorite?: (asset: Asset) => void,
    };
    

    Or to make an empty object acceptable value:

    type FavoriteAssetsContextType = {
        favoriteAssets: Array<Asset>,
        favoriteAssetIds: Array<string>,
        addFavorite: (asset: Asset) => void,
        removeFavorite: (asset: Asset) => void,
    } | {};