I have a React website and use server side rendering. My web page is displayed in the page source in the browser and also on the web page. Except the API data. This is only displayed in the page source and not on the web page. There I get the error message: Uncaught TypeError: Cannot read properties of undefined (reading ‘map’)
I have here once the server.js, where I fetch the API data and then pass it to the appropriate component. This also works correctly, because if I do console.log(data), then this data is also displayed in the console. But they are not mapped.
Leistungen.jsx
import React, { useState, useEffect } from 'react';
import { LeistungenContainer, LeistungenHeadline, LeistungenText, LeistungenKasten, TextLink, KastenLink, Titel, Headline, Text, Button } from './Leistungen.elements'
import { FaSign } from 'react-icons/fa'
import { TiBusinessCard } from 'react-icons/ti'
import { GiPapers } from 'react-icons/gi'
import { AiTwotoneBoxPlot } from 'react-icons/ai'
import { BsBoxFill } from 'react-icons/bs'
const leistungenIconsLinks = [
{Leistungsart: "Werbeanlagen & Beschilderung", icon: <FaSign size={50} color="white"/>, link: "/werbeanlagen"},
{Leistungsart: "Werbeartikel", icon: <TiBusinessCard size={50} color="white"/>, link: "/werbeartikel"},
{Leistungsart: "Folierung & Drucken", icon: <GiPapers size={50} color="white"/>, link: "/folierung"},
{Leistungsart: "Verschiedene Banner", icon: <AiTwotoneBoxPlot size={50} color="white"/>, link: "/banner"},
{Leistungsart: "Leuchtkasten & Leuchtreklame", icon: <BsBoxFill size={50} color="white"/>, link: "/leuchtkasten"}
];
const Leistungen = ({data}) => {
return (
<>
<LeistungenContainer>
<LeistungenHeadline>
<Headline>Unsere<br /><span>Leistungen</span></Headline>
<LeistungenText>Test</LeistungenText>
</LeistungenHeadline>
{data.map((leistung, i) => {
const iconLink = leistungenIconsLinks.find(item => item.Leistungsart === leistung.attributes.Leistungsname);
return (
<LeistungenKasten>
<KastenLink to={iconLink.link}>
{iconLink.icon}
<Titel>{leistung.attributes.Leistungsname}</Titel>
<Text>{leistung.attributes.Leistungsbeschreibung}</Text>
<Button to={iconLink.link}>Erfahre mehr</Button>
</KastenLink>
</LeistungenKasten>
);
})}
</LeistungenContainer>
</>
)
}
export default Leistungen
server.js
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import express from 'express'
import { Helmet } from 'react-helmet';
import fetch from 'node-fetch';
import https from 'https'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const isTest = process.env.VITEST
export async function createServer(
root = process.cwd(),
isProd = process.env.NODE_ENV === 'production',
hmrPort,
) {
const resolve = (p) => path.resolve(__dirname, p)
const indexProd = isProd
? fs.readFileSync(resolve('dist/client/index.html'), 'utf-8')
: ''
const app = express()
/**
* @type {import('vite').ViteDevServer}
*/
let vite
if (!isProd) {
vite = await (
await import('vite')
).createServer({
root,
logLevel: isTest ? 'error' : 'info',
server: {
middlewareMode: true,
watch: {
// During tests we edit the files too fast and sometimes chokidar
// misses change events, so enforce polling for consistency
usePolling: true,
interval: 100,
},
hmr: {
port: hmrPort,
},
},
appType: 'custom',
})
// use vite's connect instance as middleware
app.use(vite.middlewares)
} else {
app.use((await import('compression')).default())
app.use(
(await import('serve-static')).default(resolve('dist/client'), {
index: false,
}),
)
}
const agent = new https.Agent({
rejectUnauthorized: false
});
app.use('*', async (req, res) => {
try {
const url = req.originalUrl
let template, render
if (!isProd) {
// always read fresh template in dev
template = fs.readFileSync(resolve('index.html'), 'utf-8')
template = await vite.transformIndexHtml(url, template)
render = (await vite.ssrLoadModule('/src/entry-server.jsx')).render
} else {
template = indexProd
// @ts-ignore
render = (await import('./dist/server/entry-server.js')).render
}
const context = {}
const response = await fetch('https://hamburger-werbeagentur.de/admin/api/startseite-leistungens', { agent });
const data = (await response.json()).data;
const appHtml = render(url, context, data);
const helmet = Helmet.renderStatic();
if (context.url) {
// Somewhere a `<Redirect>` was rendered
return res.redirect(301, context.url)
}
const html = template
.replace(`<!--app-html-->`, appHtml)
.replace(`<!--app-head-->`, `${helmet.title.toString()}${helmet.meta.toString()}`)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
!isProd && vite.ssrFixStacktrace(e)
console.log(e.stack)
res.status(500).end(e.stack)
}
})
return { app, vite }
}
if (!isTest) {
createServer().then(({ app }) =>
app.listen(5175, () => {
console.log('http://localhost:5175')
}),
)
}
2
Answers
What exactly is in the
data
argument passed to the Leistungen component? The case might be that as the state changes, at some pointundefined
is passed into it. The easiest solution would be to use optional chaining (question mark operator) like this:This ensures that the
map
is called only whendata
is not undefined/null. However, a better solution would be to render something like aLoading
component when data is not available.Use data && data operator or optional chaining like this data?.map(()),
once requesting data from server you must use && operator to hold the flow untill the response completes