Search code examples
reactjstypescriptreact-typescript

Property processes does not exist on type "props | undefined"


i'm learning React Context and i'm using a project that i've build as a test.

After i created my context i'm trying to use it on a component where the following error is showing: Property "processes" does not exist on type "Props | undefined". But it does exist on the interface and it is also on ProcessContext.tsx as well.

interface.tsx

export default interface Props {
  children?: any;
  processes?: Array<string>;
  setProcesses?: (active: any) => void;
  updating?: boolean;
  setUpdating?: (active: boolean) => void;
  getProcesses?: () => void;
}

ProcessContext.tsx

import { createContext, FC, useState } from "react";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase";
import Props from "../Interface/Api";

const ProcessContext = createContext<Props | undefined>(undefined);
export const ProcessProvider: FC<Props> = ({ children }) => {
  const [processes, setProcesses] = useState<Array<any>>([]);
  const [updating, setUpdating] = useState<boolean>(false);

  const getProcesses = async () => {
    const processCollectionRef = collection(db, "processes");
    const data = await getDocs(processCollectionRef);
    setProcesses(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
  };

  return (
    <ProcessContext.Provider
      value={{ processes, setProcesses, updating, setUpdating, getProcesses }}
    >
      {children}
    </ProcessContext.Provider>
  );
};

export default ProcessContext;

CourseTable.tsx

import React, { FC, Fragment, useState, useContext, useEffect } from "react";
import CheckIcon from "@mui/icons-material/Check";
import {
  Modal,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
} from "@mui/material";
import ModalData from "./ModalData";
import ProcessContext from "../context/ProcessContext";
import Props from "../Interface/Api";

const CourseTable: FC = () => {
  const { processes, updating, getProcesses } = useContext(ProcessContext);
  const [open, setOpen] = useState(false);
  const handleOpen = () => setOpen(true);
  const handleClose = () => setOpen(false);

  useEffect(() => {
    getProcesses();
  }, [updating]);
}

Solution

  • The issue is that when you create the context, you specify that its type can be undefined. createContext() takes a default value for the context when a component calls it without a provider in its ancestors. You set this value to be undefined as well.

    const ProcessContext = createContext<Props | undefined>(undefined);
    

    This is a totally valid way to initialize a context, it makes sense that the context could be undefined if there's no provider in the component's ancestors. A component doesn't "know" whether or not there's a provider as an ancestor to provide values for the context it's trying to use.

    However, if your context is undefined, then the expression

    const { processes } = useContext(ProcessContext);
    

    results in a javascript error, which is what Typescript is warning you about. This warning will only become a javascript error if you call your context from a component without access to a provider. Still nice that Typescript can catch that before it becomes an error.

    There are two ways to fix this:

    1. Handle the possibility of the context value being undefined when you call useContext()
    2. Initialize your context with a default value other than undefined

    For 1. the code could be the following:

    const processContextValue = useContext(ProcessContext);
    if (processContextValue) {
      const { processes, ... } = processContextValue;
      // Do stuff with processes
    }
    

    That code is just one of the options, there are many ways to handle undefined values. You can check the concept of narrowing a type (figuring out a type from a union) in the typescript docs.

    For 2. a common thing to do is initialize each member in the context object (all items specified in your Props interface) with a sensible default value (which could also be undefined) instead.

    const ProcessContext = createContext<Props>({
      children: undefined,
      processes: [], // You can always use `undefined`, but you can also use a default value like an empty array
      setProcesses: undefined,
      updating: false,
      setUpdating: undefined, // Functions could be `undefined`
      getProcesses: () => null, // or a default function that satisfies the type
    });
    

    I also noticed you made every field optional, there's no need to just because you are using Context, they only have to be optional if undefined is a possible value.