Home > Software engineering >  obj.map is not a function issue for ReactJS
obj.map is not a function issue for ReactJS

Time:11-15

I am trying to write a frontend for my API but I stuck at some point and couldn't figure out the solution. I get the issue that my "getResult" can not invoke map function at rendering. I tried everything and I end up with nothing.

So my Filter component as in the following. At the end of the render I implement GameTable component which is given after this codeblock.

import React, { useState, useEffect } from 'react' 
import { useQuery } from 'react-query'; 
import gameService  from '../services/gameService' 
import GameTable from './GameTable' 
import '../App.css'

const Filter = () => {

const [getName, setGetName] = useState("")
const [getGenre, setGetGenre] = useState("")
//const [getPublisher, setGetPublisher] = useState("")
const [metadata,setMetadata] = useState([])

const [getResult, setGetResult] = useState([])

const fortmatResponse = (res) => {
  return JSON.stringify(res,null, 2);
}

const {isLoading: isLoadingGames, refetch: getAllGames} = useQuery("query-games",
  async() => {
    return await gameService.get("/games");
  },
  {
    enabled: false,
    onSuccess: (res) => {
        const result = {
          status : res.status   "-"   res.statusText,
          headers: res.headers,
          data: res.data,
        };
        setMetadata(fortmatResponse(result.data.metadata));
        setGetResult(fortmatResponse(result.data.games));
    },
    one rror : (err) => {
      setGetResult(fortmatResponse(err.response?.data || err));
    },
  }
);

useEffect(() => {
  if (isLoadingGames) setGetResult("loading...");
},[isLoadingGames]);

function getAllData() {
  try{
    getAllGames();
  }catch(err) {
    setGetResult(fortmatResponse(err));
  }
}

const {isLoading: isLoadingGame, refetch: getGamesByName} = useQuery(
  "query-games-by-name",
  async () => {
    return await gameService.get(`/games?name=${getName}`);
  },
  {
    enabled: false,
    onSuccess: (res) => {
        const result = {
          status : res.status   "-"   res.statusText,
          headers: res.headers,
          data: res.data,
        };
        setMetadata(fortmatResponse(result.data.metadata));
        setGetResult(fortmatResponse(result.data.games));
    },
    one rror : (err) => {
      setGetResult(fortmatResponse(err.response?.data || err));
    },
  }
);

useEffect(() => {
  if (isLoadingGame) setGetResult("loading...");
}, [isLoadingGame]);

function getDataByName() {
  if (getName) {
    try {
      getGamesByName();
    } catch (err) {
      setGetResult(fortmatResponse(err));
    }
  }
}

const {isLoading: isSearchingGame, refetch: findGamesByGenre} = useQuery(
  "query-games-by-genre",
  async () => {
    return await gameService.get(`/games?genre=${getGenre}`);
  },
  {
    enabled: false,
    onSuccess: (res) => {
      const result = {
        status : res.status   "-"   res.statusText,
        headers: res.headers,
        data: res.data,
      };
      setMetadata(fortmatResponse(result.data.metadata));
      setGetResult(fortmatResponse(result.data.games));
    },
    one rror : (err) => {
      setGetResult(fortmatResponse(err.response?.data || err));
    },
  }
);

useEffect(() => {
  if (isSearchingGame) setGetResult("loading...");
}, [isSearchingGame]);

function getDataByGenre() {
  if (getGenre) {
    try {
      findGamesByGenre();
    } catch (err) {
      setGetResult(fortmatResponse(err));
    }
  }
}

const clearGetOutput =() => {
  setGetResult([]);
}
console.log(metadata)

return(
<div className='card'>
    <div className='card-header input-group-sm'> GET Request </div>
    <div className='card-body'>
      <div className='input-group input-group-sm'>
        <button className='btn btn-sm btn-primary' onClick={getAllData}>
          Get All
        </button>
        <input
          type="text"
          value={getName}
          onChange={(e) => setGetName(e.target.value)}
          className='form-control ml-2'
          placeholder='Name'
          />
        <div className='input-group-append'>
          <button className="btn btn-sm btn-primary" onClick={getDataByName}>
            Get by Name
          </button>
        </div>
        <input
            type="text"
            value={getGenre}
            onChange={(e) => setGetGenre(e.target.value)}
            className="form-control ml-2"
            placeholder="Genre"
            />
         <div className="input-group-append">
          <button className="btn btn-sm btn-primary" onClick={getDataByGenre}>
            Find By Genre
          </button>
        </div>
        <button className="btn btn-sm btn-warning ml-2" onClick={clearGetOutput}>
          Clear
        </button>
      </div>
      <GameTable games={getResult} />
      
    </div>
  </div>
  )
}

export default Filter;

Here is my GameTable.js

import React from 'react'
import Game from './Game'
import '../App.css'

const GameTable = ({games}) => {
    return (
        <div className="table-wrapper">
            <h2>Games</h2>
                <table className='fl-table'>
                <thead>
                    <tr>
                        <th> Name </th>
                        <th> Genre </th>
                        <th> Publisher </th>
                    </tr>
                </thead>
                <tbody>
                    {
                        games.map(game =>
                            <Game key={game.id} game={game} />
                        )
                    }     
                </tbody>
                </table>
        </div> 
    )
}
export default GameTable;

And Game.js is as following.

import React from 'react'

const Game = ({game}) => {
    
    return (
        <tr>
            <td>{game.name}</td>
            <td>{game.genre}</td>
            <td>{game.publisher_name}</td>
        </tr>
    )
}
export default Game

I am sure this is pretty straight-forward for some of you but I really stuck. Any helps and tips will be much appreciated. Thanks.

I have changed the initial state from null to [] but that didn't help.

CodePudding user response:

You're calling .map() in your GameTable component here:

games.map(game =>
  <Game key={game.id} game={game} />
)

.map() is a function on arrays, so this will only work is games` is an array.

Is it?

It comes from the component props:

const GameTable = ({games}) => {

And is passed as a prop from the parent component:

<GameTable games={getResult} />

So what is getResult? It's initialized to an array:

const [getResult, setGetResult] = useState([])

So that will work fine on the first render. But where do you call setGetResult and what do you set it to? It turns out that you call it pretty much everywhere. For example, here you set it to a string:

setGetResult("loading...")

A string isn't an array, so this will fail. You also set it to a string here:

setGetResult(fortmatResponse(result.data.games))

That is, fortmatResponse returns a string. A string isn't an array, so this will fail. You also set it to... something here:

setGetResult(fortmatResponse(err.response?.data || err))

Basically you're using getResult as a generic placeholder for any and all possible data you might want to store somewhere. So it's failing on the .map() operation any time that data isn't an array.

Remove all of the instances where you set getResult to a string. If you have an array of data, set it to that array:

setGetResult(result.data.games)

But keep it as an array. Keep it as the data you intend for it to be. (And choose a better name, maybe something like "games", to indicate what that data is meant to represent. That will help keep you from confusing yourself in your own code.)

Basically... If your value should be an array, keep it as an array. The code isn't necessarily failing, you're just using inconsistent data.

CodePudding user response:

you must to add 'return' in the callaback of the map

  • Related