0

I'm learning Next.js and I'm having difficulties accessing the result of one of my API routes with getStaticProps. The working API route produces an undefined when I console.log the props object in my component.

My API route returns a GeoJSON object from a Postgres database. I can access the results at localhost:3000/api/mymethod, and I've tested the result in a GeoJSON validator.

I was able to construct my map with 2 components: a component for the map container itself and a second component for my Canvas layer. The map component is then imported into a page.

This is how I constructed the component for the Map Container:

import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import 'leaflet-defaulticon-compatibility';
import LeafletCanvasMarker from './Pengs';

const Map = () => {
  return (
    <MapContainer center={[50.1109, 8.6821]} zoom={14} scrollWheelZoom={false} style={{height: "100vh", width: "100%"}}>
      <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
  />
      <Marker 
      position={[50.1109, 8.682]}
      draggable={true}
      animate={true}
      >
      </Marker>
      <LeafletCanvasMarker />
    </MapContainer>
  )
}

export default Map

An this is my failing Canvas Layer component:

import { useEffect } from "react";
import { useMap } from "react-leaflet";
import "leaflet-canvas-marker";
import L from "leaflet";

export default function LeafletCanvasMarker({features}) {
  console.log({features})
  const map = useMap();
 
  useEffect(() => {
    if (!map) return;

    var ciLayer = L.canvasIconLayer({}).addTo(map);

    ciLayer.addOnClickListener(function (e, data) {
      console.log(data);
    });
    ciLayer.addOnHoverListener(function (e, data) {
      console.log(data[0].data._leaflet_id);
    });

    var icon = L.icon({
      iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png",
      iconSize: [20, 18],
      iconAnchor: [10, 9],
    });
    ciLayer.addLayers({features});
  }, [map]);

  return null;
}

export async function getStaticProps() {
  const data = await fetch('.api/getpoints').then(r => r.json())

  return {
    props:{ features }
    }
}

Here is the page with the map component

import Head from 'next/head'
import dynamic from 'next/dynamic';
const mapPage = () => {
    const MapWithNoSSR = dynamic(() => import("../components/Map"), {
        ssr: false
    });
    
    return (
        <div>
          <Head>
            <title>About</title>
          </Head>
          <h1>Map of Peng spots</h1>
          <p>Map should go here!</p>
          <MapWithNoSSR />
        </div>
    )
}

export default mapPage  

My API route is defined in /api/getpoints in an index.js file with the following code:

export default async function handler(req, res) { 
try{
const query = `SELECT json_build_object(
    'type', 'FeatureCollection',
    'features', jsonb_agg(feature)
)
FROM (
SELECT json_build_object(
'type', 'Feature',
    'geometry', ST_AsGeoJSON(geom)::JSONB,
    'properties', to_jsonb(inputs) - 'geometry'
) AS feature
FROM (
SELECT *
FROM public."Peng" AS p
JOIN public."Address_Info" AS i
ON p.id = i.id
) AS inputs
) AS features;`
const result = await conn.query(
query)
res.json(result.rows[0].json_build_object)
} 
catch (error){
    console.log(error);
}
}

When you visit localhost/api/getpoints in the browser, valid GeoJSON is returned.

The code above worked with some dummy data, but I'm having issues with getting the API data into the component. Being relatively new to React and Next, I'm not sure where I have tripped up with this.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
nizz0k
  • 471
  • 1
  • 8
  • 23
  • I cant see where you are rendering your map component? is this the full code? – Matt Aug 15 '22 at 15:02
  • I didn't see it as relevant to the question but I've added it to the question now. My api code might be more relevant, but it works and returns a valid GeoJSON object, I just don't know why it's undefined when using the getStaticProps... – nizz0k Aug 15 '22 at 16:16
  • 1
    `getStaticProps` only works in page components, it won't run in regular components. In your case the `getStaticProps` in `LeafletCanvasMarker` will never run. You need to define `getStaticProps` in your map page and pass it down to the component that needs the data. See [NextJS getStaticProps() never called](https://stackoverflow.com/questions/69075289/nextjs-getstaticprops-never-called). – juliomalves Aug 16 '22 at 17:05
  • 1
    Also note that you shouldn't call internal API routes from inside `getStaticProps`. You should use the logic that's in the route directly in `getStaticProps`. See [Fetch error when building Next.js static website in production](https://stackoverflow.com/questions/66202840/fetch-error-when-building-next-js-static-website-in-production). – juliomalves Aug 16 '22 at 17:09
  • 1
    Spent sometime with the docs and finally realized that. This gets glossed in some of the tutorials as they're all using an external API. – nizz0k Aug 17 '22 at 07:33

2 Answers2

0

Try changing your getStaticProps to something like this. You can then use mapPoints or whatever you want to call it

    export async function getStaticProps() {
      const data = await fetch('/api/getpoints');
      const mapPoints = await data.json();
    
      return {
        props:{ 
          features,
          mapPoints
         }
        }
    }

Try changing your api code to this.

export default async function handler(req, res) { 
    try{
        const query = `SELECT json_build_object(
            'type', 'FeatureCollection',
            'features', jsonb_agg(feature)
        )
        FROM (
        SELECT json_build_object(
        'type', 'Feature',
            'geometry', ST_AsGeoJSON(geom)::JSONB,
            'properties', to_jsonb(inputs) - 'geometry'
        ) AS feature
        FROM (
        SELECT *
        FROM public."Peng" AS p
        JOIN public."Address_Info" AS i
        ON p.id = i.id
        ) AS inputs
        ) AS features;`
        const result = await conn.query(
        query)
        return(
            res.status(200).json({
                result: result.rows[0].json_build_object,
            })
        )
    } 
    catch (error){
        console.log(error);
        return(
            res.status(500).json({
                message: 'Error',
            })
        )
    }
}
Matt
  • 791
  • 4
  • 13
  • So, your answer helped in that it fixed my error with the API route URL, but the values for both features and mapPoints are coming back undefined... – nizz0k Aug 15 '22 at 16:49
  • @nizz0k can you console.log(mapPoints) inside getStaticProps and comment the answer for me please – Matt Aug 15 '22 at 16:57
  • {features: undefined, mapPoints: undefined} – nizz0k Aug 15 '22 at 17:13
  • I added my api route above as well – nizz0k Aug 15 '22 at 17:21
  • @nizz0k check my updated answer and let me know – Matt Aug 15 '22 at 17:30
  • still undefined, no error on the fetch – nizz0k Aug 15 '22 at 17:39
  • I'm beginning to wonder if this is something that has to get called on the page itself? – nizz0k Aug 15 '22 at 17:50
  • @nizz0k Just do one last console log for me add console.log(result) in the api just before the return. – Matt Aug 15 '22 at 18:39
  • I'll have to dig into the debugger for that but I don't see what you'd be looking for there. The route works, hitting it in the browser produces valid JSON – nizz0k Aug 16 '22 at 06:53
  • @nizz0k then result.rows[0].json_build_object is not correct. If you log the result and it returns something in the console then its working you are just returning the wrong item – Matt Aug 16 '22 at 09:02
  • So, it's not in the console but in the browser. My geojson is nested within there, which I why I'm accessing it there on the object. With that formatting, hitting localhost:3000/api/getpoints produces the correctly formatted geojson that I want (2000+ features). I just want the object that API route (correctly) returns to be used in my map component. I am clearly misunderstanding something about how to access the API data. When I log the preformatted result object, I get the methods and db response as a part of it as expected, I just can't get the result of that route in my component. – nizz0k Aug 16 '22 at 11:19
0

So, the answer was in one of the initial comments: getStaticProps one works on page components. Calling the function in other files doesn't work.

nizz0k
  • 471
  • 1
  • 8
  • 23