获取多边形搜索功能时遇到问题将不会显示 create-react-app 中 API 调用的结果

问题描述 投票:0回答:0

我对代码没有经验,这可能是我犯这个错误的原因——我不知道我在这里发布的代码是否正确,所以请原谅我的新手..

但无论如何我都在努力使这项工作成功。我有一个应用程序从 api 获取列表,返回并在网页上显示它们。有一张地图,我正在尝试向其添加多边形搜索(这样我就可以在地图上绘制以获取我的结果)但我无法让它工作,关于地图/javascript 需要数组中的数据,但是需要它的 api 调用(来自多边形的坐标)在一个字符串中。所以我不知道如何让这两部分一起工作。

每当我尝试将数据作为数组传递以便它显示在列表中时,我都会收到此错误,但随后 api(GET /propertyextendedsearch 'polygon' 参数)返回错误的格式错误。当我拥有现在的组件时,所有 api 调用都按预期工作,但多边形搜索不返回任何结果(没有显示我可以看到的错误,只是地图或列表上没有显示结果)

错误 polygonCoords.map 不是函数 TypeError: polygonCoords.map 不是函数 在

在处理FinishDrawing

在新班级。

下面是相关代码(部分与问题无关,仅供参考):

`//Listings jsx
import React, { useState, useEffect } from "react";
import Map from "../components/map/Map";
import SearchBar from "../components/searchBar/SearchBar";
import ListingList from "../components/listingsList/ListingList";
import LoadingSpinner from '../components/loading/LoadingSpinner';
import FilterBar from "../components/filterBar/FilterBar";
import L from "leaflet";
import leafletPip from "leaflet-pip";

const Listings = ({ fetchListings, loading, filters, query, setFilters }) => {

  const [filteredData, setFilteredData] = useState([]);
  const [polygonCoords, setPolygonCoords] = useState("");
  const [searchType, setSearchType] = useState("");
  const isListingInPolygon = (listing, coords) => {
    if (!coords || !Array.isArray(coords) || coords.length === 0) return true;

    const point = L.latLng(listing.latitude, listing.longitude);
    const polygon = L.polygon(coords, { color: 'blue' });

    const layerGroup = L.layerGroup().addLayer(polygon);

    return leafletPip.pointInLayer(point, layerGroup).length > 0;
  };

  const defaultCenter = { lat: 45.5152, lng: -122.6784 };
  const [searchedCenter, setSearchedCenter] = useState(defaultCenter);

  useEffect(() => {
    if (query === "" && searchType === "") return; // Add this line

    const searchByPolygon = polygonCoords && polygonCoords.length > 0;

    const fetchData = async () => {
      if (searchByPolygon) {
        const data = await fetchListings("", filters, polygonCoords, "polygon");
        if (data && data.length > 0) {
          const filteredListings = data.filter((listing) =>
            isListingInPolygon(listing, polygonCoords)
          );
          setFilteredData(filteredListings);
        } else {
          setFilteredData([]);
        }
      } else {
        const data = await fetchListings(query, filters, null, "location");
        setFilteredData(data);
      }
    };

    fetchData();
  }, [filters, fetchListings, query, polygonCoords]);



  const handleFiltersChange = (newFilters) => {
    setFilters(newFilters);
  };

  return (
    <div className="listings">
      <SearchBar
        fetchListings={(query) => fetchListings(query, filters)}
        setSearchedCenter={setSearchedCenter}
        defaultCenter={defaultCenter}
        setSearchType={setSearchType} // Add this line
      />
      <FilterBar
        filters={filters}
        onFiltersChange={handleFiltersChange}
        fetchListings={fetchListings}
        query={query}
      />
      <div className="listingsPage">
        <Map
          center={searchedCenter}
          zoom={13}
          listings={filteredData}
          setPolygonCoords={setPolygonCoords}
          setSearchType={setSearchType}
          fetchListings={fetchListings}
          query={query}
          searchType={searchType} // Add this line
        />
        {loading ? (
          <LoadingSpinner />
        ) : (
          <ListingList listings={filteredData} />
        )}
      </div>
    </div>
  );
};
export default Listings;`

`//Map jsx (Leaflet Final))
import React, { useState, useEffect } from "react";
import InfoMarker from "../infoMarker/InfoMarker";
import ListingCard from "../listingCard/ListingCard";
import { MapContainer, TileLayer, Marker as LeafletMarker, Popup, useMap } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import PolygonHandler from "./PolygonHandler";
import "@geoman-io/leaflet-geoman-free";
import "@geoman-io/leaflet-geoman-free/dist/leaflet-geoman.css";
import L from "leaflet";


import "./Map.css";

function ChangeView({ center, zoom }) {
  const map = useMap();
  useEffect(() => {
    map.setView(center, zoom);
  }, [center, zoom, map]);

  return null;
}

const Map = ({
  center,
  zoom,
  listings,
  polygonCoords,
  setPolygonCoords,
  setSearchType,
  fetchListings,
  filters,
  searchType,
}) => {
  const [centerObj, setCenter] = useState(center);
  const [selected, setSelected] = useState(null);

  const [openModalZpid, setOpenModalZpid] = useState(null);
  const openModal = (zpid) => {
    setOpenModalZpid(zpid);
  };

  const handleClick = () => {
    setSelected(null);
  };

  useEffect(() => {
    if (listings && listings.length > 0) {
      const firstValidListing = listings.find((listing) => listing.latitude && listing.longitude);
      if (firstValidListing) {
        const obj = { lat: firstValidListing.latitude, lng: firstValidListing.longitude };
        setCenter(obj);
      } else if (searchType !== "polygon") {
        setCenter(center);
      }
    } else if (searchType !== "polygon") {
      setCenter(center);
    }
  }, [listings, center, searchType])

  const handleFinishDrawing = (polygonCoordsStr) => {
    console.log("Polygon coordinates string:", polygonCoordsStr);

    if (!polygonCoordsStr) {
      console.error("Polygon coordinates string is undefined");
      return;
    }

    const polygonCoords = polygonCoordsStr
      .split(",")
      .map((coordStr) => {
        const [lng, lat] = coordStr.trim().split(" ");
        return L.latLng(parseFloat(lat), parseFloat(lng));
      });

    // Add the first coordinate to the end of the array to close the polygon
    polygonCoords.push(polygonCoords[0]);

    // Format the polygon coordinates into a string for the API
    const formattedPolygonCoords = polygonCoords
      .map((coord) => `${coord.lng}%20${coord.lat}`)
      .join("%2C");

    console.log("Polygon coordinates:", polygonCoords);

    setSearchType("polygon");

    // Remove 'polygon=' from the parameter string
    const polygonParam = formattedPolygonCoords;
    console.log("Polygon parameter being sent to the API:", polygonParam); // Log the final string

    fetchListings("", filters, polygonCoords, "polygon");
    console.log("Finished drawing polygon");
  };


  return (
    <>
      {selected &&
        <InfoMarker info={selected} handleClick={handleClick} openModal={setOpenModalZpid} />
      }
      <div className="leafletMapReact" style={{ height: "85vh", width: "50%" }}>
        <MapContainer center={centerObj} zoom={zoom} style={{ height: "85vh", maxWidth: "100%", maxHeight: "100%" }}>
          <ChangeView center={centerObj} zoom={zoom} />
          <TileLayer
            url="https://tile.jawg.io/9357cba3-7e9d-4a74-ad30-1c2a39c27360/{z}/{x}/{y}{r}.png?access-token=VT0F6yZuMFa7Mnk9psknNdbaX994oc629Rd7cA4B8Bg1Jf5H1l2FQtvBrG5GFq0F"
            attribution='<a href="https://www.jawg.io" target="_blank">&copy; Jawg</a> - <a href="https://www.openstreetmap.org" target="_blank">&copy; OpenStreetMap</a> contributors'
          />
          {listings &&
            listings
              .filter((listing) => listing.latitude && listing.longitude)
              .map((listing) => (
                <LeafletMarker
                  key={listing.zpid}
                  position={[listing.latitude, listing.longitude]}
                  eventHandlers={{
                    click: () => {
                      setSelected({
                        id: listing.zpid,
                        price: listing.price,
                        img: listing.imgSrc,
                        bed: listing.bedrooms,
                        bath: listing.bathrooms,
                        sqft: listing.livingArea,
                        address: listing.address,
                      });
                    },
                  }}
                />
              ))}
          <PolygonHandler
            setPolygonCoords={setPolygonCoords}
            onFinishDrawing={handleFinishDrawing}
            setSearchType={setSearchType}
          />
        </MapContainer>
      </div>
    </>
  );
};

Map.defaultProps = {
  center: {
    lat: 45.523064,
    lng: -122.676483,
  },
  zoom: 11,
};


export default Map`
`// App.js

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import axios from "axios";
import Home from "./routes/Home";
import Navbar from "./components/navbar/NavBar";
import Listings from "./routes/Listings";
import Footer from "./components/footer/Footer";
import LoadingSpinner from "./components/loading/LoadingSpinner";
import React, { useState, useCallback, Suspense } from "react";
import axiosRateLimit from "axios-rate-limit";

const api = axiosRateLimit(axios.create(), { maxRequests: 1, perMilliseconds: 1000 }); // set the maximum number of API requests to 1 per second

function App() {
  const [listings, setListings] = useState([]);
  const [loading, setLoading] = useState(false);
  const [filters, setFilters] = useState({
    minPrice: "",
    maxPrice: "",
    bedsMin: "",
    bedsMax: "",
    bathsMin: "",
    bathsMax: "",
    sqftMin: "",
    sqftMax: "",
    buildYearMin: "",
    buildYearMax: "",
    lotSizeMin: "",
    lotSizeMax: "",
    daysOn: "",
    status_type: "",
    home_type: "",
    sort: "",
  });
  const [searchType, setSearchType] = useState("");
  const [query, setQuery] = useState("");
  const length = listings?.length;
  console.log(length + " this is the length");

  const host = "zillow-com1.p.rapidapi.com";
  const key = process.env.REACT_APP_API_BLUEFIN_ZILLOW_API_KEY;

  const [polygonCoords, setPolygonCoords] = useState(null);

  const fetchListings = useCallback(
    (searchQuery, filtersToUpdate = filters, polygonCoords, searchType) => {
      if (!searchQuery && !polygonCoords && searchType === "") return;
      setQuery(searchQuery);
      setLoading(true);
      setSearchType(searchType);

      const {
        minPrice,
        maxPrice,
        bedsMin,
        bedsMax,
        bathsMin,
        bathsMax,
        sqftMin,
        sqftMax,
        buildYearMin,
        buildYearMax,
        lotSizeMin,
        lotSizeMax,
        daysOn,
        status_type,
        home_type,
        sort,
      } = filtersToUpdate;

      let url = "";
      url = `https://zillow-com1.p.rapidapi.com/propertyExtendedSearch?`;

      if (polygonCoords) {
        const formattedPolygonCoords = polygonCoords
          .map((coord) => `${coord.lng}%20${coord.lat}`)
          .join("%2C");
        url += `&polygon=${formattedPolygonCoords}`;
      } else if (typeof searchQuery === "string") {
        url += `&location=${searchQuery}`;
      }

      if (minPrice) {
        url += `&minPrice=${minPrice}`;
      }
      if (maxPrice) {
        url += `&maxPrice=${maxPrice}`;
      }
      if (bedsMin) {
        url += `&bedsMin=${bedsMin}`;
      }
      if (bedsMax) {
        url += `&bedsMax=${bedsMax}`;
      }
      if (bathsMin) {
        url += `&bathsMin=${bathsMin}`;
      }
      if (bathsMax) {
        url += `&bathsMax=${bathsMax}`;
      }
      if (sqftMin) {
        url += `&sqftMin=${sqftMin}`;
      }
      if (sqftMax) {
        url += `&sqftMax=${sqftMax}`;
      }
      if (buildYearMin) {
        url += `&buildYearMin=${buildYearMin}`;
      }
      if (buildYearMax) {
        url += `&buildYearMax=${buildYearMax}`;
      }
      if (lotSizeMin) {
        url += `&lotSizeMin=${lotSizeMin}`;
      }
      if (lotSizeMax) {
        url += `&lotSizeMax=${lotSizeMax}`;
      }
      if (daysOn) {
        url += `&daysOn=${daysOn}`;
      }
      if (status_type) {
        url += `&status_type=${status_type}`;
      }
      if (home_type) {
        url += `&home_type=${home_type}`;
      }
      if (sort) {
        url += `&sort=${sort}`;
      }

      const options = {
        method: 'GET',
        url,
        headers: {
          'x-rapidapi-key': key,
          'x-rapidapi-host': host,
        },
      };

      return api
        .request(options)
        .then((response) => {
          let props = response.data.props;

          if (props?.length === 0) {
            setListings([]);
            setLoading(false);
            return [];
          }

          setListings(props);
          setLoading(false);
          return props;
        })
        .catch((error) => {
          console.error(error);
          setListings([]);
          setLoading(false);
          return [];
        });
    },
    [filters, key]
  );
  const handleClearFilters = useCallback(() => {
    setFilters({
      minPrice: "",
      maxPrice: "",
      bedsMin: "",
      bedsMax: "",
      bathsMin: "",
      bathsMax: "",
      sqftMin: "",
      sqftMax: "",
      buildYearMin: "",
      buildYearMax: "",
      lotSizeMin: "",
      lotSizeMax: "",
      daysOn: "",
      status_type: "",
      home_type: "",
      sort: "",
    });
  }, []);

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Navbar />
      {loading && <LoadingSpinner />}
      <Router>
        <Routes>
          <Route
            exact
            path="/"
            element={
              <Home
                fetchListings={fetchListings}
                listings={listings}
                loading={loading}
              />
            }
          />
          <Route
            path="/listings"
            element={
              <Listings
                fetchListings={fetchListings}
                listings={listings}
                loading={loading}
                filters={filters}
                query={query}
                setFilters={setFilters}
                handleClearFilters={handleClearFilters}
                polygonCoords={polygonCoords}
                setPolygonCoords={setPolygonCoords}
                searchType={searchType}
                setSearchType={setSearchType}
              />
            }
          />

        </Routes>
      </Router>
      <Footer />
    </Suspense>
  );
}

export default App;`

`// SearchBar jsx
import React, { useState } from "react";
import { AiOutlineSearch } from "react-icons/ai";

import "./SearchBar.css";

const SearchBar = ({ fetchListings, setSearchType, }) => {
  const [query, setQuery] = useState("");

  const onSubmit = (e) => {
    e.preventDefault();
    setSearchType(""); 
    fetchListings(query);
  };

  const handleInputChange = (event) => {
    setQuery(event.target.value);
  };
  
  return (
    <div className="searchBar">
      <form className="sBar" onSubmit={onSubmit}>
        <input
          value={query}
          onChange={handleInputChange}
          type="text"
          placeholder=" City, Address, School, Agent, ZIP"
        />
        <button type="submit">
          <AiOutlineSearch className="icon" />
        </button>
      </form>
    </div>
  );
};

export default SearchBar;
//PolygonHandler.jsx
...
if (allowDrawing) {
  options.drawPolygon = true;
}

map.pm.addControls(options);

map.pm.setGlobalOptions({
  edit: {
    snappable: true,
    snapDistance: 20,
  },
});

if (allowDrawing) {
  map.pm.enableDraw("");
}
map.on("pm:create", (event) => {
  const coords = event.layer
    .getLatLngs()[0]
    .map((latLng) => `${latLng.lng} ${latLng.lat}`)
    .reverse()
    .join(",");
  setPolygonCoords(coords);
  if (onFinishDrawing) {
    onFinishDrawing(coords);
  }
});

return () => {
  map.pm.removeControls();
  map.off("pm:create");
}; },[map, setPolygonCoords, allowDrawing, onFinishDrawing]);
return null;
};

export default PolygonHandler;

`

`

    // SearchBar jsx
    import React, { useState } from "react";
    import { AiOutlineSearch } from "react-icons/ai";

    import "./SearchBar.css";

const SearchBar = ({ fetchListings, setSearchType, }) => {
  const [query, setQuery] = useState("");

  const onSubmit = (e) => {
    e.preventDefault();
    setSearchType("");
    fetchListings(query);
  };

  const handleInputChange = (event) => {
    setQuery(event.target.value);
  };

  return (
    <div className="searchBar">
      <form className="sBar" onSubmit={onSubmit}>
        <input
          value={query}
          onChange={handleInputChange}
          type="text"
          placeholder=" City, Address, School, Agent, ZIP"
        />
        <button type="submit">
          <AiOutlineSearch className="icon" />
        </button>
      </form>
    </div>
  );
};

export default SearchBar;

`

//ListingList jsx
import React from 'react'
import ListingCard from '../listingCard/ListingCard'

import "./ListingList.css"

const ListingList = ({ listings }) => {
return (
<div className='listingListSection ml-1'>
{listings && listings.map((listing) => (
<ListingCard key={listing.zpid} listing={listing} />
))}
</div>
)
}

export default ListingList;
`

`//InfoMarker jsx
import React from "react";
import { Modal, Card, Button } from "flowbite-react";
import { RiCloseFill } from "react-icons/ri";
import L from "leaflet";
import "beautifymarker/leaflet-beautify-marker-icon.css";
import "beautifymarker";

import "./InfoMarker.css";

const InfoMarker = ({ info, handleClick, openModal, setSelected }) => {
  const icon = L.BeautifyIcon.icon({
    icon: "home",
    iconShape: "marker",
    borderColor: "#f00",
    textColor: "#f00",
  });

  return (
    <Modal id="infoMarkerModal" show className="max-w-[75vmin] h-auto">
      <div className="popupContainer max-w-[80vmin] h-auto">
        <div>
          <Card imgSrc={info.img} className="h-full w-full" />
        </div>
        <div className="popupRight flex flex-col justify-between p-4 w-full">
          <div>
            <h5 className="text-2xl font-bold text-gray-900 dark:text-white">${info.price.toLocaleString()}</h5>
            <p className="font-bold text-gray-900 dark:text-white">{info.address}</p>
            <p className="text-gray-700 dark:text-gray-400">{info.bed} Beds | {info.bath} Baths | {info.sqft} Sqft </p>
          </div>
          <div className="flex justify-end">
            <Button onClick={handleClick} className="mr-2">
              Close
            </Button>
            <Button
              onClick={() => openModal(info.id)}
              className="bg-blue-600 hover:bg-blue-700 text-white"
            >
              More Details
            </Button>
          </div>
        </div>
        <div className="closeIcon absolute right-0 top-0 mt-2 mr-2">
          <RiCloseFill
            onClick={handleClick}
            className="text-white text-2xl cursor-pointer"
          />
        </div>
      </div>
      <L.Marker
        position={[info.latitude, info.longitude]}
        icon={icon}
        eventHandlers={{
          click: () => {
            setSelected({
              id: info.zpid,
              price: info.price,
              img: info.imgSrc,
              bed: info.bedrooms,
              bath: info.bathrooms,
              sqft: info.livingArea,
              address: info.address,
            });
          },
        }}
      />
    </Modal>
  );
};

export default InfoMarker;
`
javascript axios create-react-app geocoding react-leaflet
© www.soinside.com 2019 - 2024. All rights reserved.