'A delete button in React component cannot get the useState from page

I am making a Google Map API app. But I have a trouble calling the setMarker(useState) from the component.

It said, Uncaught TypeError: Cannot read properties of undefined (reading 'filter').

I want to make a table to show my search location results and store to the useState(markers). It works, however, when I want to delete the location results by click the button, it's not work. What should I do to make a delete button. Thanks

My Map.js/App.js

import React, { useState, useEffect, useRef } from "react";
import {
  GoogleMap,
  useJsApiLoader,
  Marker,
  StandaloneSearchBox,
} from "@react-google-maps/api";

import Tablerecord from "../components/Tablerecord";

const libraries = ["places"];

const containerStyle = {
  width: "100%",
  height: "400px",
};

const inputStyle = {
  width: "100%",
  height: "500px",
};

const center = {
  lat: 22.7658314,
  lng: -22.4137297,
};

const options = {
  disableDefaultUI: true,
  zoomControl: true,
};

function App() {
  const [markers, setMarkers] = useState([]);
  const searchBox = useRef(null);

  const [searchPlaceCenter, setSearchPlaceCenter] = useState([
    parseFloat(43.7658314),
    parseFloat(-79.4137297),
  ]);

  const { isLoaded } = useJsApiLoader({
    id: "google-map-script",
    googleMapsApiKey: "API-KEY",
    libraries,
  });

  return isLoaded ? (
    <div>
      <StandaloneSearchBox
        onLoad={(ref) => (searchBox.current = ref)}
        onPlacesChanged={async (event) => {
          await // Find the search field value and make it be center value.
          setSearchPlaceCenter([
            parseFloat(
              searchBox.current.getPlaces()[0].geometry.location.lat()
            ),
            parseFloat(
              searchBox.current.getPlaces()[0].geometry.location.lng()
            ),
          ]);
          //  Store Search geolocation value to useState markers for Red markers
          setMarkers((current) => [
            ...current,
            {
              lat: searchBox.current.getPlaces()[0].geometry.location.lat(),
              lng: searchBox.current.getPlaces()[0].geometry.location.lng(),
              name: searchBox.current.getPlaces()[0].formatted_address,
              time: new Date(),
            },
          ]);
        }}
      >
        <input
          type="text"
          name="address"
          placeholder="Search Address"
          autoComplete="off"
          style={{
            boxSizing: `border-box`,
          }}
        ></input>
      </StandaloneSearchBox>

      <GoogleMap
        mapContainerStyle={containerStyle}
        zoom={8}
        //  Set center to searchPlace location to change center
        center={{
          lat: parseFloat(searchPlaceCenter[0]),
          lng: parseFloat(searchPlaceCenter[1]),
        }}
        options={options}
      >
        {markers.map((marker) => (
          <Marker
            key={marker.time.toISOString()}
            position={{
              lat: parseFloat(marker.lat),
              lng: parseFloat(marker.lng),
            }}
          />
        ))}
      </GoogleMap>

      {/* !!!!!!!!Here is my question !!!!!!*/}
      {markers.map((marker) => (
        <div>
          <Tablerecord key={marker.time} marker={marker} />
        </div>
      ))}
    </div>
  ) : (
    <div>Loading...</div>
  );
}

export default React.memo(App);

My Tablerecord.js(component)

import React, { useState } from "react";
import styles from "../styles/tablerecord.module.css";

const Tablerecord = (marker,{markers,setMarkers}) => {


  const handleRemoveLocation = (e) => {
    const name = e.target.getAttribute("name");
    console.log(markers) // undefined
    setMarkers(markers.filter((item) => item.name !== name));
  };

  return (
    <div className={styles.table}>
      <center>
          {/* This work, can show name,lat and lng */}
        <p name={marker.marker.name}>Name: {marker.marker.name}</p>
        <p>
          lat: {marker.marker.lat}, lng: {marker.marker.lng}, Added Time:{" "}
          {marker.marker.time.toTimeString()}
        </p>

        {/* Delete button I want to ask */}
        <button name={marker.name} onClick={handleRemoveLocation}>
          x
        </button>
      </center>
    </div>
  );
};

export default Tablerecord;


Solution 1:[1]

You're close, but not quite sending through the right props to the table record component.

<Tablerecord key={marker.time} marker={marker} markers={markers} setMarkers={setMarkers} />

Then,

const Tablerecord = (props) => {
   const { marker, markers, setMarkers } = props; 
   // ...
}

or if you prefer:

const Tablerecord = ({ marker, markers, setMarkers }) => {
   // ...
}

Solution 2:[2]

Thank you for all the above comments!

At last, I adjust the code of the "Tablerecord" component and few of "Map.js".

Here is my code

Tablerecord.js(component)

import React, { useState } from "react";
import styles from "../styles/tablerecord.module.css";

const Tablerecord = ({ marker, markers, setMarkers }) => {
  const handleRemoveLocation = (e) => {
    const name = e.target.getAttribute("name");
    //   markers will output an Object to use .filter
    // filter out the item which is equal to the name.
    setMarkers(markers.filter((item) => item.name !== name));
  };

  return (
    <div className={styles.table}>
      <center>
        {/* String type - name , lat, lng and time */}
        <p>Name: {marker.name}</p>
        <p>
          lat: {marker.lat}, lng: {marker.lng}, Added Time:{" "}
          {marker.time.toTimeString()}
        </p>
        {/* marker.name can let setMarkers detect the name */}
        <button name={marker.name} onClick={handleRemoveLocation}>
          x
        </button>
      </center>
    </div>
  );
};

export default Tablerecord;

Map.js

<Tablerecord key={marker.time} marker={marker} markers={markers} setMarkers={setMarkers} />

Solution 3:[3]

I believe you need to add the states into the component

<Tablerecord key={marker.time} marker={marker} markers={markers} setMarkers={setMarkers} />

Solution 4:[4]

You can access the grid using ViewChild and then access its columns field:

import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { GridComponent } from "@progress/kendo-angular-grid";

@Component({
    selector: 'my-app',
    template: `
        <kendo-grid #grid [data]="gridData">
            <kendo-grid-column field="Id" title="ID">
            </kendo-grid-column>
            <kendo-grid-column field="CompanyName" title="Company Name">
            </kendo-grid-column>
        </kendo-grid>
    `
})
export class AppComponent implements AfterViewInit {
    @ViewChild("grid") grid: GridComponent;

    public readonly gridData = [{
        'Id': 'ALFKI',
        'CompanyName': 'Alfreds Futterkiste',
        'ContactName': 'Maria Anders',
        'ContactTitle': 'Sales Representative',
        'City': 'Berlin'
    }, {
        'Id': 'ANATR',
        'CompanyName': 'Ana Trujillo Emparedados y helados',
        'ContactName': 'Ana Trujillo',
        'ContactTitle': 'Owner',
        'City': 'México D.F.'
    }, {
        'Id': 'ANTON',
        'CompanyName': 'Antonio Moreno Taquería',
        'ContactName': 'Antonio Moreno',
        'ContactTitle': 'Owner',
        'City': 'México D.F.'
    }];

    ngAfterViewInit() {
        const columns = this.grid.columns.map((column) => {
            return {
                field: column.field,
                title: column.title
            };
        });

        console.log(columns);
    }
}

Note: this.grid.columns gives you an object of type QueryList<ColumnBase>. The ColumnBase class doesn't contain the field field in its definition, however, it does appear on the object. I guess that this.grid.columns actually returns an object of type QueryList<ColumnComponent>. ColumnComponent derives from ColumnBase and does contain the field field.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Martijn Pieters
Solution 4 Shai