javascript - Egenskapen "handle" eksisterer ikke på typen "undefined" - react-kontext og typescript


Jeg konverterer appen min fra JS til TS. Alt har fungert bra under JS, men når jeg begynte konverteringen til TS, får jeg mange feil med håndteringsfunksjoner som for eksempel handleVideoAdd. Har noen en idé om hva jeg gjør galt? Har prøvd mange ting uten suksess...

Egenskapen 'handleVideoAdd' eksisterer ikke på typen 'undefined'. TS2339 - og det peker på dette kodefragmentet:

    const { handleVideoAdd, inputURL, handleInputURLChange } =  useContext(Context)

Koden min ser slik ut:

  • Header.tsx
    import { Context } from "../Context";
    import React, { useContext } from "react";
    import { Navbar, Button, Form, FormControl } from "react-bootstrap";

export default function Header() {
  const { handleVideoAdd, inputURL, handleInputURLChange } =
    useContext(Context);
  return (
    <Navbar bg="light" expand="lg">
      <Navbar.Brand href="#home">Video App</Navbar.Brand>
      <Form onSubmit={handleVideoAdd} inline>
        <FormControl
          type="text"
          name="url"
          placeholder="Paste url"
          value={inputURL}
          onChange={handleInputURLChange}
          className="mr-sm-2"
        />
        <Button type="submit" variant="outline-success">
          Add
        </Button>
      </Form>
    </Navbar>
  );
}
  • Context.tsx
import { useEffect, useMemo, useState } from "react";
import { youtubeApi } from "./APIs/youtubeAPI";
import { vimeoApi } from "./APIs/vimeoAPI";
import React from "react";
import type { FormEvent } from "react";

const Context = React.createContext(undefined);

function ContextProvider({ children }) {
  const [inputURL, setInputURL] = useState("");
  const [videoData, setVideoData] = useState(() => {
    const videoData = localStorage.getItem("videoData");
    if (videoData) {
      return JSON.parse(videoData);
    }
    return [];
  });
  const [filterType, setFilterType] = useState("");
  const [videoSources, setVideoSources] = useState([""]);
  const [wasSortedBy, setWasSortedBy] = useState(false);
  const [showVideoModal, setShowVideoModal] = useState(false);
  const [modalData, setModalData] = useState({});
  const [showWrongUrlModal, setShowWrongUrlModal] = useState(false);

  const createModalSrc = (videoItem) => {
    if (checkVideoSource(videoItem.id) === "youtube") {
      setModalData({
        src: `http://www.youtube.com/embed/${videoItem.id}`,
        name: videoItem.name,
      });
    } else {
      setModalData({
        src: `https://player.vimeo.com/video/${videoItem.id}`,
        name: videoItem.name,
      });
    }
  };

  const handleVideoModalShow = (videoID) => {
    createModalSrc(videoID);
    setShowVideoModal(true);
  };
  const handleVideoModalClose = () => setShowVideoModal(false);

  const handleWrongUrlModalShow = () => setShowWrongUrlModal(true);

  const handleWrongUrlModalClose = () => setShowWrongUrlModal(false);

  const handleInputURLChange = (e) => {
    setInputURL(e.currentTarget.value);
  };

  const handleVideoAdd = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const source = checkVideoSource(inputURL);

    if (source === "youtube") {
      handleYouTubeVideo(inputURL);
    } else if (source === "vimeo") {
      handleVimeoVideo(inputURL);
    } else {
      handleWrongUrlModalShow();
    }
  };

  const checkVideoSource = (inputURL) => {
    if (inputURL.includes("youtu") || inputURL.length === 11) {
      return "youtube";
    } else if (inputURL.includes("vimeo") || inputURL.length === 9) {
      return "vimeo";
    }
  };

  const checkURL = (inputURL) => {
    if (!inputURL.includes("http")) {
      const properURL = `https://${inputURL}`;
      return properURL;
    } else {
      return inputURL;
    }
  };

  const checkInputType = (inputURL) => {
    if (!inputURL.includes("http") && inputURL.length === 11) {
      return "id";
    } else if (!inputURL.includes("http") && inputURL.length === 9) {
      return "id";
    } else {
      return "url";
    }
  };

  const fetchYouTubeData = async (videoID) => {
    const data = await youtubeApi(videoID);
    if (data.items.length === 0) {
      handleWrongUrlModalShow();
    } else {
      setVideoData((state) => [
        ...state,
        {
          id: videoID,
          key: `${videoID}${Math.random()}`,
          name: data.items[0].snippet.title,
          thumbnail: data.items[0].snippet.thumbnails.medium.url, //default, medium, high
          viewCount: data.items[0].statistics.viewCount,
          likeCount: data.items[0].statistics.likeCount,
          savedDate: new Date(),
          favourite: false,
          source: "YouTube",
          url: inputURL,
        },
      ]);
      setInputURL("");
    }
  };

  const handleYouTubeVideo = (inputURL) => {
    const inputType = checkInputType(inputURL);

    if (inputType === "id") {
      fetchYouTubeData(inputURL);
    } else {
      const checkedURL = checkURL(inputURL);
      const url = new URL(checkedURL);
      if (inputURL.includes("youtube.com")) {
        const params = url.searchParams;
        const videoID = params.get("v");
        fetchYouTubeData(videoID);
      } else {
        const videoID = url.pathname.split("/");
        fetchYouTubeData(videoID[1]);
      }
    }
  };

  const fetchVimeoData = async (videoID) => {
    const data = await vimeoApi(videoID);

    if (data.hasOwnProperty("error")) {
      handleWrongUrlModalShow();
    } else {
      setVideoData((state) => [
        ...state,
        {
          id: videoID,
          key: `${videoID}${Math.random()}`,
          name: data.name,
          thumbnail: data.pictures.sizes[2].link, //0-8
          savedDate: new Date(),
          viewCount: data.stats.plays,
          likeCount: data.metadata.connections.likes.total,
          savedDate: new Date(),
          favourite: false,
          source: "Vimeo",
          url: inputURL,
        },
      ]);
      setInputURL("");
    }
  };

  const handleVimeoVideo = (inputURL) => {
    const inputType = checkInputType(inputURL);

    if (inputType === "id") {
      fetchVimeoData(inputURL);
    } else {
      const checkedURL = checkURL(inputURL);
      const url = new URL(checkedURL);
      const videoID = url.pathname.split("/");
      fetchVimeoData(videoID[1]);
    }
  };

  const deleteVideo = (key) => {
    let newArray = [...videoData].filter((video) => video.key !== key);
    setWasSortedBy(true);
    setVideoData(newArray);
  };

  const deleteAllData = () => {
    setVideoData([]);
  };

  const toggleFavourite = (key) => {
    let newArray = [...videoData];
    newArray.map((item) => {
      if (item.key === key) {
        item.favourite = !item.favourite;
      }
    });
    setVideoData(newArray);
  };

  const handleFilterChange = (type) => {
    setFilterType(type);
  };

  const sourceFiltering = useMemo(() => {
    return filterType
      ? videoData.filter((item) => item.source === filterType)
      : videoData;
  }, [videoData, filterType]);

  const sortDataBy = (sortBy) => {
    if (wasSortedBy) {
      const reversedArr = [...videoData].reverse();
      setVideoData(reversedArr);
    } else {
      const sortedArr = [...videoData].sort((a, b) => b[sortBy] - a[sortBy]);
      setWasSortedBy(true);
      setVideoData(sortedArr);
    }
  };

  const exportToJsonFile = () => {
    let dataStr = JSON.stringify(videoData);
    let dataUri =
      "data:application/json;charset=utf-8," + encodeURIComponent(dataStr);

    let exportFileDefaultName = "videoData.json";

    let linkElement = document.createElement("a");
    linkElement.setAttribute("href", dataUri);
    linkElement.setAttribute("download", exportFileDefaultName);
    linkElement.click();
  };

  const handleJsonImport = (e) => {
    e.preventDefault();
    const fileReader = new FileReader();
    fileReader.readAsText(e.target.files[0], "UTF-8");
    fileReader.onload = (e) => {
      const convertedData = JSON.parse(e.target.result);
      setVideoData([...convertedData]);
    };
  };

  useEffect(() => {
    localStorage.setItem("videoData", JSON.stringify(videoData));
  }, [videoData]);

  return (
    <Context.Provider
      value={{
        inputURL,
        videoData: sourceFiltering,
        handleInputURLChange,
        handleVideoAdd,
        deleteVideo,
        toggleFavourite,
        handleFilterChange,
        videoSources,
        sortDataBy,
        deleteAllData,
        exportToJsonFile,
        handleJsonImport,
        handleVideoModalClose,
        handleVideoModalShow,
        showVideoModal,
        modalData,
        showWrongUrlModal,
        handleWrongUrlModalShow,
        handleWrongUrlModalClose,
      }}
    >
      {children}
    </Context.Provider>
  );
}
export { ContextProvider, Context };

  • App.js (ikke konvertert til TS ennå)
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css";
import reportWebVitals from "./reportWebVitals";
import { ContextProvider } from "./Context.tsx";

ReactDOM.render(
  <React.StrictMode>
    <ContextProvider>
      <App />
    </ContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

Svar #1

Dette er fordi når du opprettet konteksten din, satte du standardverdien til undefined.

Dette skjer her: const Context=React.createContext(undefined)

Du kan ikke si undefined.handleVideoAdd. Men du kan teoretisk sett si {}.handleVideoAdd.

Så hvis du standardiserer konteksten din til {} i starten som dette: const Context=React.createContext({})

Appen din bør ikke krasje med en gang lenger.

REDIGER: Jeg ser at du bruker TypeScript, i så fall må du lage et grensesnitt for konteksten din. Noe sånt som dette:

interface MyContext {
  inputURL?: string,
  videoData?: any,
  handleInputURLChange?: () => void,
  handleVideoAdd?: () => void,
  deleteVideo?: () => void,
  // and all the rest of your keys
}

Da, når du lager konteksten din, gjør dette:

const Context = React.createContext<MyContext>({});