contentful / contentful.js

JavaScript library for Contentful's Delivery API (node & browser)
https://contentful.github.io/contentful.js
MIT License
1.19k stars 200 forks source link

Uncaught (in promise) TypeError: Cannot read property 'fields' of undefined #410

Closed Perdixo75 closed 4 years ago

Perdixo75 commented 4 years ago

I'm trying to create a "portfolio" website to learn react. I've plugged content from Contentul, but i'm getting an error : Uncaught (in promise) TypeError: Cannot read property 'fields' of undefined when trying to display my content.

Here's what i've done so far to get content from Contentful into my React app :

  1. I've creacted a contentful.js file
    
    ## contentful.js

const client = require('contentful').createClient({ space: 'MYSPACEID', accessToken: 'MYACCESSTOKEN', });

const getProjectItems = () => client.getEntries().then((response) => response.items);

const getSingleProject = (slug) => client .getEntries({ 'fields.slug': slug, content_type: 'project', }) .then((response) => response.items);

export { getProjectItems, getSingleProject };


Then, i've created 2 custom Hooks for getting my content :
```javascript
## UseProjects.js

import { useEffect, useState } from 'react';

import { getProjectItems } from '../contentful';

const promise = getProjectItems();

export default function useProjects() {
  const [projects, setProjects] = useState([]);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
    promise.then((project) => {
      setProjects(project);
      setLoading(false);
    });
  }, []);

  return [projects, isLoading];
}
## useSingleProject.js

import { useEffect, useState } from 'react';

import { getSingleProject } from '../contentful';

export default function useSingleProject(slug) {
  const promise = getSingleProject(slug);

  const [project, setProject] = useState(null);
  const [isLoading, setLoading] = useState(true);

  useEffect(() => {
    promise.then((result) => {
      setProject(result[0].fields);
      setLoading(false);
    });
  }, [promise]);

  return [project, isLoading];
}

I can add my components code if needed but i feel like my error comes from here.. What's weird is that if i close the error, i see all the items properly rendered (so..they're properly pulled from Contentful) and if i click on it i've got the correct informations displayed (title, image, etc.). But the error makes weird layout things.

The error comes from my useSingleProject.js file (useSingleProject.js:13)

Now here i feel it can also come from my App.js file, i'm not sure about how i configured the routing for single project pages (i'm still new to react..). If i disable the following line from the routes array : { path: '/:id', name: ':id', Component: SingleProject }, then the error disapears. I can see all the projects on my projects page, but if i click on one of them the slug changes but nothing shows on the single project pages, since i've disabled it.

## App.js

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import { gsap } from 'gsap';
import './styles/App.scss';
import Header from './components/header';
import Navigation from './components/navigation';

import CaseStudies from './pages/caseStudies';
import Approach from './pages/approach';
import Services from './pages/services';
import About from './pages/about';
import Home from './pages/home';
import Projects from './pages/projects';
import SingleProject from './pages/SingleProject';

const routes = [
  { path: '/', name: 'Home', Component: Home },
  { path: '/case-studies', name: 'caseStudies', Component: CaseStudies },
  { path: '/approach', name: 'approach', Component: Approach },
  { path: '/services', name: 'services', Component: Services },
  { path: '/about-us', name: 'about', Component: About },
  { path: '/projects', name: 'projects', Component: Projects },
  { path: '/:id', name: ':id', Component: SingleProject },
];

function debounce(fn, ms) {
  let timer;
  return () => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      fn.apply(this, arguments);
    }, ms);
  };
}

function App() {
  const [dimensions, setDimensions] = React.useState({
    height: window.innerHeight,
    width: window.innerWidth,
  });

  useEffect(() => {
    // prevents flashing
    gsap.to('body', 0, { css: { visibility: 'visible' } });
    const debouncedHandleResize = debounce(function handleResize() {
      setDimensions({
        height: window.innerHeight,
        width: window.innerWidth,
      });
    }, 1000);

    window.addEventListener('resize', debouncedHandleResize);
    return () => {
      window.removeEventListener('resize', debouncedHandleResize);
    };
  });
  return (
    <>
      <Header dimensions={dimensions} />
      <div className="App">
        {routes.map(({ path, Component }) => (
          <Route key={path} exact path={path}>
            <Component dimensions={dimenasions} />
          </Route>
        ))}
      </div>
      <Navigation />
    </>
  );
}

export default App;

EDIT : So i've tried to console.log(response.items) in my getSingleProject function. It returns the correct array of object (so here containing only one object).

I've also tried tu console.log(result) in my useProjects function (inside the useEffect). It still logs the correct object, and it has the fields property i need to get. When console logging in my useEffect, it logs the object every second or so by the way. Is this a normal behavior?

Perdixo75 commented 4 years ago

So after another day trying to understand where the error comes from, it looks like it's coming from the way i've setup my Router in App.js. It's not an issue with my Contenful config. By modifying the way i call my routes, the error disappeared and now all content is pulled and displayed correctly. For those interested, here is my new App.js file and how i've setup routes for now to correct the issue :

import React, { useEffect } from 'react';
import { Switch, Route } from 'react-router-dom';
import { gsap } from 'gsap';
import './styles/App.scss';
import Header from './components/header';
import Navigation from './components/navigation';

import Approach from './pages/approach';
import Services from './pages/services';
import About from './pages/about';
import Home from './pages/home';
import Projects from './pages/projects';
import SingleProject from './pages/SingleProject';

function debounce(fn, ms) {
  let timer;
  return () => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      fn.apply(this, arguments);
    }, ms);
  };
}

function App() {
  const [dimensions, setDimensions] = React.useState({
    height: window.innerHeight,
    width: window.innerWidth,
  });

  useEffect(() => {
    // prevents flashing
    gsap.to('body', 0, { css: { visibility: 'visible' } });
    const debouncedHandleResize = debounce(function handleResize() {
      setDimensions({
        height: window.innerHeight,
        width: window.innerWidth,
      });
    }, 1000);

    window.addEventListener('resize', debouncedHandleResize);
    return () => {
      window.removeEventListener('resize', debouncedHandleResize);
    };
  });
  return (
    <>
      <Header dimensions={dimensions} />
      <div className="App">
        <Switch>
          <Route path="/" exact render={(props) => <Home {...props} dimensions={dimensions} />} />
          <Route
            path="/projects"
            exact
            render={(props) => <Projects {...props} dimensions={dimensions} />}
          />
          <Route
            path="/approach"
            exact
            render={(props) => <Approach {...props} dimensions={dimensions} />}
          />
          <Route
            path="/services"
            exact
            render={(props) => <Services {...props} dimensions={dimensions} />}
          />
          <Route
            path="/about-us"
            exact
            render={(props) => <About {...props} dimensions={dimensions} />}
          />
          <Route
            path="/:id"
            render={(props) => <SingleProject {...props} dimensions={dimensions} />}
          />
        </Switch>
      </div>
      <Navigation />
    </>
  );
}

export default App;

I'll refactor to make it less repetitive but here's how i've fixed it for now.