0

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));
    },
    onError : (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));
    },
    onError : (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));
    },
    onError : (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.

Can Yolal
  • 15
  • 3
  • It seems like `games` is an Object, which does not support `map()`; try `Object.keys(games).map(...)` – mykaf Nov 14 '22 at 19:13
  • The only `.map()` operation I see in this code is `games.map()` in the `GameTable` component. Which is being passed the `getResult` value from the `Filter` component. What is that value? It's initialized as an array, but then you keep setting it to a string. Why? The error is telling you that there is no `.map()` on a string, which indeed there isn't. – David Nov 14 '22 at 19:13

2 Answers2

0

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.

David
  • 208,112
  • 36
  • 198
  • 279
  • Hi David, thanks a lot for your detailed answer. After reading your answer, it was like a magic happened and I solved the issue immediately. As you said, I didn't pay attention at all to types. Maybe that's a good sign for me to move to Typescript. Thanks again for your time, your 10 minutes taught me something invaluable that I will cherish for life. That's why I quit my job and decided to pursue a career in software development, this amazing community. Really appreciated! – Can Yolal Nov 15 '22 at 06:29
-2

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

  • (1) [No you don't.](https://stackoverflow.com/q/28889450/328193) (2) The call to the `.map()` function *itself* is producing an error, so the callback is never reached in the first place. – David Nov 14 '22 at 19:15
  • or at least debug your code in gametable component by console.log() in – Achraf Hebboul Nov 14 '22 at 19:15
  • do you try that, try to console.log(games) in gametable component @David – Achraf Hebboul Nov 14 '22 at 19:19
  • If you want to try to debug the OP's code for them, you are welcome to do that. I can observe from looking at the code that they're trying to call `.map()` on a string, which is why the error occurs. And which has nothing to do with the (objectively incorrect) suggestion made in this answer. ES6 arrow functions have an implicit return when the function is just an expression. Please refer to the linked question in my comment above to learn more. – David Nov 14 '22 at 19:23
  • As David suggested, using string type for map function was causing the issue for error scenarios and info messages. Assigning new state for messages and not converting data to string solved the issue. – Can Yolal Nov 15 '22 at 06:32