useQuery - can not read properties of undefined - reactjs

Initial query returns undefined, but all subsequent calls return valid data. If I try to map my response, an error is thrown:
can not read properties of undefined
and the whole app is down. How do i fix this?
import { useQuery } from "#apollo/client";
import { Component } from "react";
import { useState, useEffect } from "react";
import GET_PRODUCTS from '../../server/getProducts';
import './productList.sass';
class ProductList extends Component {
render() {
return (
<RenderProducts />
);
}
}
const RenderProducts = () => {
const { data } = useQuery(GET_PRODUCTS);
console.log(data.products.map(product => console.log(product)));
const products = data.products.map((product) => {
return (
<li className="productList__item">
<img className="productList__item-img" src={product.mainImage.url} alt={product.title} />
<div className="productList__item-descr">
<div className="productList__item-title">{product.title}</div>
<div className="productList__item-price">{product.price} $</div>
</div>
</li>
)
})
return <ul>{products}</ul>
}
export default ProductList;

If I try to map my response, an error is thrown:
and the whole app is down. How do i fix this?
You'll need to render something when the query is in a loading state. You can take advantage of the loading and error properties of useQuery hook. Here's a sample:
const RenderProducts = () => {
const { data, loading, error } = useQuery(GET_PRODUCTS);
if(loading) return <div>loading...</div>
if(error) return <div>cannot render products...something went wrong</div>
// if the query has finished loading products and there's no error,
// You can access data.products
// and write your logic
console.log(data.products.map(product => console.log(product)));
const products = data.products.map((product) => {
return (
<li className="productList__item">
<img className="productList__item-img" src={product.mainImage.url} alt={product.title} />
<div className="productList__item-descr">
<div className="productList__item-title">{product.title}</div>
<div className="productList__item-price">{product.price} $</div>
</div>
</li>
)
})
return <ul>{products}</ul>
}

Related

.map is not a function react, axios problem

I am having a bit of a problem using the map function.
In the below code I am fetching NFT data through a rarible api using Axios (Works perfectly fine, I get a promise with the object as a response)
import Link from 'next/link'
import { useWeb3Context } from '../../context'
import { Web3Button } from '../../components'
import axios from 'axios'
import React, { useState } from 'react'
import NFTContainer from '#/components/NFTContainer'
const renderNotConnectedContainer = () => <Web3Button />
const fetchOwnedTokens = async (owner) => {
try {
const result = await axios.get(`https://ethereum-api.rarible.org/v0.1/nft/items/byOwner?owner=${owner}`)
return [result.data.items]
} catch (err) {
console.error(err)
return []
}
}
export const ChooseProductView = () => {
const { address } = useWeb3Context()
if (!address) {
return renderNotConnectedContainer()
} else {
const data = fetchOwnedTokens(address)
console.log(data)
return (
<div className="flex items-center justify-center">
<NFTContainer nfts={data} />
</div>
)
}
}
Then I am trying to pass the response to a container file using props, but I get an error --Unhandled Runtime Error
TypeError: nfts.map is not a function
which I think is because NFTs are not of an array datatype. Any suggestions, please? Have been stuck for a while
import React from 'react'
import NFTCard from './NFTCard'
const NFTContainer = ({ nfts }) => {
return (
<div>
{nfts.map((nft, index) => (
<NFTCard nft={nft} key={index} />
))}
</div>
)
}
export default NFTContainer
You can not use map on object so you should convert it :
<div>
{Object.keys(nfts).map((nft, index) => (
<NFTCard nft={nft} key={index} />
))}
</div>

Failed to fetch the Query on reload

The data from the Query does load once it loaded but once i reload it the data does not appear... I have console.log the data by using "console.log(data.allPetInformations)" and it does work and getting an array of objects...
import { ALL_PET_INFORMATIONS } from '../../../query';
import { useQuery } from '#apollo/client';
import Session from 'react-session-api';
export function ClientPet(){
const username = Session.get('username');
const { loading, error, data } = useQuery(ALL_PET_INFORMATIONS);
if(loading) return <div>loading...</div>
if(error) return <div>{error.message}</div>
return(
<div>
{data.allPetInformations.filter(pet => pet.client.username===username).map(pet => (
<div key={pet.id}>
{pet.name}
</div>
))}
</div>
)
}

Second fetch on API - React

I'm trying to access the data of an api and I have a problem that I can't solve...
I have made a call to the api and it returns the results correctly:
export const getCharacters = async (category) => {
const url = `https://www.breakingbadapi.com/api/characters?name=${encodeURI(category)}`;
const resp = await fetch(url);
const data = await resp.json();
const characters = data.map(item => {
return {
id: item.char_id,
title: item.name,
url: item.img,
nickname: item.nickname,
bday: item.birthay,
occupation: item.occupation,
status: item.status
}
})
return characters;
}
I put the results into a card and then I add in the character card a link to another component where I want to show "more details":
import React from 'react'
import { Link } from 'react-router-dom'
import '../../index.css'
export const CharactersGridItem = ({id, title, url}) => {
return (
<div className="card-global">
<span className="col">
<span className="card" style={{width: '10rem'}}>
<span className="row">
<img src={url} className="card-img-top" alt={title} style={{ width: '270px', height: '250px'}}></img>
<h6 className="card-title">{title}</h6>
<Link
className="card-body"
to={`/Characters/${id}`}
>
Details...
</Link>
</span>
</span>
</span>
</div>
)
}
All this works correctly and takes me to the url of the character's id on which I click.
My problem is when I try to recover those other properties of the character that, curiously, I can see them in the console (before everything explodes).
Here is the hook with which I made that second "request" for data:
import { useState, useEffect } from "react";
export const useCharacter = (id) => {
const [character, setCharacter] = useState()
function getCharacter(id) {
return fetch(
`https://www.breakingbadapi.com/api/characters/${encodeURI(id)}`
);
}
useEffect(() => {
// Después de que el componente se monta, se ejecuta este callback
getCharacter(id).then((resp) => resp.json()).then(([body]) => {
//console.log(body)
setCharacter(body)
})
}, [])
//return character;
return character;
}
And this is where I bring the data:
import React from 'react'
import { useParams } from 'react-router-dom';
import { useCharacter } from '../../hooks/useCharacter';
export const CharactersDetail = (setCharacter) => {
const { id } = useParams()
const characters = useCharacter(id)
console.log(characters)
return (
<div className="container">
<div className="container-section">
<p>{characters.name}</p>
<p>{characters.nickname}</p>
</div>
</div>
)
}
When I click on a character, and go to the details page (CharacterDetails), if I don't put this code on the return...:
<p>{characters.name}</p>
<p>{characters.nickname}</p>
...everything works correctly and with console.log it prints the data json correctly (console). Even if I write the code and refresh the page (localhost:3000), it prints what I ask for.
But the moment I go back to the character page, click on another one (different id) and get to its corresponding detail page, everything explodes. An error tells me that it doesn't recognize the characters.name or characters.nickname.
Any ideas on why this might be happening?
Any help is greatly appreciated!!!
wcharacters will be undefined for the first render. Only after the data is received your components render again.
So you need to explicitly handle the case where useCharacter returns undefined.
For example:
export const CharactersDetail = (setCharacter) => {
const { id } = useParams();
const characters = useCharacter(id);
if (!characters) {
return <div>Loading...</div>;
}
return (
<div className="container">
<div className="container-section">
<p>{characters.name}</p>
<p>{characters.nickname}</p>
</div>
</div>
)
}

Display Search Results from API React/Redux

I have used Redux for state management in my application - with React Hooks. I am able to hit the API and get the response back like below screenshots from my action creator logs:
Here's the code of my component where I'm trying to display the results:
import { useState, useEffect } from "react";
import {Link} from 'react-router-dom';
import styled from "styled-components";
import {GoSearch} from 'react-icons/go';
import LoaderSpinner from "../components/LoaderSpinner";
import MovieItem from "../components/MovieItem";
import { RootStateOrAny, useDispatch, useSelector } from "react-redux";
import {fetchAllShows} from '../actions/movies';
import {searchMovieByTitle} from '../actions/search';
const Home = () => {
const [loading, setLoading] = useState(true);
const [searchString, setSearchString] = useState('');
const [isFromSearchResults, setIsFromSearchResults] = useState(false);
const dispatch = useDispatch();
const movies = useSelector((state: RootStateOrAny) => state.shows)
const searchResults = useSelector((state: RootStateOrAny) => state.shows);
useEffect(()=> {
setLoading(true);
dispatch(fetchAllShows());
setIsFromSearchResults(false);
}, [dispatch])
const handleSearchChange = (e: any) => {
e.preventDefault();
setSearchString(e.target.value);
}
const findMovieByTitle = () => {
dispatch(searchMovieByTitle(searchString));
setIsFromSearchResults(true);
setSearchString('');
}
console.log(isFromSearchResults);
var start, max, paginatedArr=[], pageSize = 25;
for(start = 0; max = movies.length, start < max; start += pageSize) {
paginatedArr = movies.slice(start, start + pageSize);
}
return <HomeContainer>
<div className="search-bar">
<input
type="text"
placeholder="Search for a movie"
value={searchString}
onChange={handleSearchChange}
/>
<div className="search" onClick={findMovieByTitle}>
<GoSearch />
</div>
</div>
<div className="grid">
{
isFromSearchResults
? <div>
{
searchResults.map((result: any, index: number) => {
console.log(result);
// console.log(result.show);
return <Link
key={index}
to={{pathname:`/movies/${result.show.id}`,
state: {movie: result.show}}}
>
<MovieItem show={result.show} />
</Link>
})
// errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
}
</div>
: movies.length == 0
? <div className="loader">
<LoaderSpinner
isLoading={loading}
loadingText="Fetching Movies..."
/>
</div>
// : movies.map((movie:any, index:number) => {
: paginatedArr.map((movie:any, index:number) => {
return <Link
to={{pathname:`/movies/${movie.id}`,
state: {movie: movie}}} key={index}
>
<MovieItem show={movie} />
</Link>
})
}
</div>
</HomeContainer>
}
export default Home;
Here's the code for my action creator to make the search API call:
import {
SEARCH_MOVIE_BY_TITLE,
} from './types';
import ShowsService from '../services/ShowsService';
export const searchMovieByTitle = (title: string) => async (dispatch: any) => {
try {
let response = await ShowsService.searchMovieByTitle(title);
console.log(typeof(response.data));
// console.log(response.data);
const promise = response.data.map((items: any) => {
// console.log(items);
return items;
})
const searchArr = await Promise.all(promise);
console.log(searchArr);
dispatch({type: SEARCH_MOVIE_BY_TITLE, payload: searchArr});
} catch (err) {
console.log(err);
}
}
The problem now lies in trying to parse the search results array and display it as a list of <MyComponent /> with movie passed as props. It shows undefined for each of the items passed. How do I resolve this?
Sample error log attached below:
I looked into your code and it seems your logs were only in the searchMovieByTitle action. It seems there are no checks for the availability of data in the rendered view. Usually when you perform fetching actions, you also check if the data has been fetched successfully before starting to use it.
In your code, this could be done like this:
<div className="grid">
{isFromSearchResults && searchResults?.length ? ( // added check for searchResult here, to make sure it has data
<div>
{
searchResults.map((result: any, index: number) => {
console.log(result);
console.log(result.show?.id); // it is suggested to also check if the id exists in all the result.show objects
return (
<Link
key={index}
to={{
pathname: `/movies/${result.show?.id}`, // optionally add the "?." before id in order to avoid crushes if the id doesn't exist, though this depends on your backend logic; if they must always have an id, no need to add the "?."
state: { movie: result.show },
}}
>
<MovieItem show={result.show} />
</Link>
);
})
// errors out in this return statement. It says the result (from array mapped out above is null) whereas the action creator is able to print out the full search queries
}
</div>
) : movies.length == 0 ? (
<div className="loader">
<LoaderSpinner isLoading={loading} loadingText="Fetching Movies..." />
</div>
) : (
// : movies.map((movie:any, index:number) => {
paginatedArr.map((movie: any, index: number) => {
return (
<Link
to={{ pathname: `/movies/${movie.id}`, state: { movie: movie } }}
key={index}
>
<MovieItem show={movie} />
</Link>
);
})
)}
</div>
If you look closely, I've added a check for the length of the array, making sure the data is in there before starting to map it.
Also added a check for the id, to see if all your result.show objects have it.

React - UseState not triggering rerender following fetch inside useEffect hook

I am just working through my first React tutorial using hooks - I am using trying to fetch local data within useEffect and then update the state using useState. I am then passing the state into a Context.provider which a child element (Card.js) subscribes to. Even though useEffect runs, the Card component and the state isn't being rerendered/updated. What am I doing wrong?
MainContext.js -
import React, { createContext, useState, useContext, useEffect, Fragment } from 'react';
import List from '../containers/List';
export const myContext= createContext();
export const MainContext = ({children}) => {
const [films, setFilms] = useState([]);
const loadData = () => {
try {
fetch('../src/assets/data.json').then( result => result.json()).then(movies => {
setFilms(films)
})
} catch (error) {
console.log('there has been an error')
}}
useEffect(() => {
loadData()
},[]);
return (
<myContext.Provider value={{films,setFilms }}>
{children()}
</myContext.Provider>
);
}
Card.js -
function Card() {
const {films} = useContext(myContext)
if (films !== undefined) {
return (
<div>
{ films.map((movie,i) => {
return (
<div key={i}>
<img src={movie.img.src} className='card-img-top' alt={movie.img.alt} />
<div className='card-body'>
<h2 className='card-title'>{`#${movie.ranking} - ${movie.title} (${movie.year})`}</h2>
</div>
<ul className='list-group list-group-flush'>
<li className='list-group-item'>{`Distributor: ${movie.distributor}`}</li>
<li className='list-group-item'>{`Amount: ${movie.amount}`}</li>
</ul></div>
)
})
}
</div>
)
} else {
return <div>{'Update Failed'}</div>
}
}
export default Card
You don't have any dependency in your useEffect array. This is why it doesn't trigger again once the app is mounted. You need to pass it a dependency, so it can run again each time the dependency value changes. Also, consider adding async before your loadData function.

Resources