0

Whenever I get data on my page, after a few seconds, my whole react app disappears as in the root div in the html is left completely empty like this <div id="root"></div> as if there is nothing. This is happening on all my other projects too even when I create a new one, this disappearing of the react keeps happening sometimes even without adding any logic, it refuses to render plain html. The errors I get for now on this current project on the console is this

characters.map is not a function

I know not what could be causing this but my code looks like this for now starting with the App.js file. I am extracting data from an api.

import {BrowserRouter, Route, Routes} from "react-router-dom"
import Home from "./components/Home";

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Home />} />
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

And then followed by the CharactersListing page which is supposed to render all the characters of the show

import React, {useEffect, useState} from 'react'
import CharacterCard from './CharacterCard'

export default function BadListings() {
    const [characters, setCharacters] = useState([])
    
    useEffect(() => {
        const getData = async () => {
            await fetch("https://www.breakingbadapi.com/api/characters")
                .then(response => {
                    setCharacters(response.json());
                    console.log(characters);
                })
                .catch(err => console.log("There must have been an error somewhere in your code", err.message));
        }
        getData();
    });
    
    
  return (
    <div className='container'>
        {characters.map(character => (
            <div>
                <CharacterCard name={character.name} status={character.status} image={character.img} />
            </div>
        ))}
    </div>
  )
}

And finally, the CharacterCard.js

import React from 'react'
import "../styles/styles.css"

export default function CharacterCard({name, status, image}) {
  return (
    <div className='card'>
        <h1>{name}</h1>
        <h2>{status}</h2>
        <img src={image} alt="umfanekiso" className='imgur' />
    </div>
  )
}

I do not know what could be causing this. I have never had this issue it just started today. What could be causing it

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Vuyi
  • 96
  • 3
  • 14

4 Answers4

2

Issues

The issue is that you are not setting the characters state to what you think it is. response.json() returns a Promise object and doesn't have a map property that is a function to be called.

The useEffect hook is also missing a dependency, so anything that triggers this BadListings component to rerender will also retrigger this useEffect hook, which updates state and triggers another rerender. The code is likely render looping.

Solution

  • The code should wait for the response.json() Promise to resolve and pass that result value into the characters state updater function. Note that I've also rewritten the logic to use async/await with try/catch as it is generally considered anti-pattern to mix async/await with Promise chains.
  • Add a dependency array to the useEffect hook. Since I don't see any dependencies use an empty array so the effect runs only once when the component mounts.

Promise chain Example:

useEffect(() => {
  fetch("https://www.breakingbadapi.com/api/characters")
    .then(response => response.json()) // <-- wait for Promise to resolve
    .then(characters => setCharacters(characters)
    .catch(err => {
      console.log("There must have been an error somewhere in your code", err.message)
    });
}, []); // <-- add empty dependency array

async/await Example:

useEffect(() => {
  const getData = async () => {
    try {
      const response = await fetch("https://www.breakingbadapi.com/api/characters");
      const characters = await response.json(); // <-- wait for Promise to resolve
      setCharacters(characters);
    } catch(err) {
      console.log("There must have been an error somewhere in your code", err?.message);
    };
  }
  getData();
}, []); // <-- add empty dependency array

Don't forget to add a React key to the mapped characters:

{characters.map((character) => (
  <div key={character.char_id}> // <-- Add React key to outer element
    <CharacterCard
      name={character.name}
      status={character.status}
      image={character.img}
    />
  </div>
))}

Edit why-does-my-react-app-disappear-when-i-run-it

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 1
    @DrewReese thank you very much man it is working now I get it very much appreciated. The characters naming convention confuses me though we named our state characters and creating another variable called characters in the function does not create conflict how come? – Vuyi Apr 14 '22 at 17:35
  • 1
    @Vuyi Variable [scope](https://developer.mozilla.org/en-US/docs/Glossary/Scope) and Javascript [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures). We can declare a locally scoped variable. This OFC makes the outer scoped one inaccessible. Some linters, i.e. SonarLint, may warn about "masking" variables like this. – Drew Reese Apr 14 '22 at 17:58
  • 1
    @Vuyi You *could* do something like `setCharacters(await response.json())` but it's arguable that this hurts readability and violates the Single Responsibility Principle, in this case being that the 1 line of code is doing 2 things instead of being split between two lines of code each doing a single task. – Drew Reese Apr 14 '22 at 18:03
0

characters is a string and strings don't have .map() method, that's why React is crashing. And since React's crashed, it couldn't mount generated HTML to the #root.

You can use [...strings] to use .map() method.

Umut Gerçek
  • 630
  • 6
  • 9
0

Great instinct to look for errors in the console.

Umut Gerçek's answer is correct, but I'd add an additional suggestion: if you're going to map over something, you should instantiate it in state as a thing that can be mapped. Set its initial state to an array:

const [characters, setCharacters] = useState([])

Note the capital 'C' in the setter; that is the useState convention.

Then set characters as you're currently doing:

setCharacters(response.json());

And your map should work regardless of the result of your fetch, and handle multiple items, too.

Jake Worth
  • 5,490
  • 1
  • 25
  • 35
  • Thanks for the response, I have changed the useState to an array but now it has just changed to "Cannot read properties of undefined (reading 'map')" and also the main problem still persists which is it still refuses to render anything even plain html – Vuyi Apr 14 '22 at 15:25
  • Is there any place you're setting `characters` to `undefined`? – Jake Worth Apr 14 '22 at 15:27
  • no. I am only using it only on that CharacterListings.js file only. It only changes when I loop over it in the map – Vuyi Apr 14 '22 at 15:33
  • hey hope you're good there realised there's no private messaging feature on this site sorry to bother you but may you please assist me in this problem if you can https://stackoverflow.com/questions/72063603/react-with-firebase-authentication-site-rendering-empty-page – Vuyi Apr 29 '22 at 21:15
0

You are exporting CharacterCards and not BadCards.

Please change all BadCards in your CharactersListing page to CharacterCards

There is an explaination What does "export default" do in JSX?