I have a problem with my API call. I want to create a search bar for my project and the data comes from an API. But, when I add a filter function I am getting an error.
TypeError: Cannot read properties of undefined (reading 'filter')
To fix it, I added optional chaining operator and it didn't work either because I get "undefined". Also, to catch the value I used a useEffect hook but I am not sure whether it is the right solution. I think my component is rendered before my API call is not executed. Can you help me to fix it?
This is my main component:
import "../App.css"
import APICall from "./APICall.js"
import Filter from "./Filter.js"
import {useState} from "react"
function App() {
const [datum, setDatum] = useState([])
return (
<div className="App">
<Filter datum={datum} />
<APICall setDatum={setDatum} />
</div>
)
}
export default App
This is child component when I make API call:
import React from 'react'
import axios from "axios"
import { useState, useEffect } from 'react'
function APICall({setDatum}) {
const [loading, setLoading] = useState(false)
useEffect(() => {
const fetchAPI = async () => {
setLoading(true)
try {
const res = await axios(`https://api.spoonacular.com/food/products/search?query=yogurt&apiKey=`)
setDatum(res.data.products)
// console.log(datum)
}
catch(err) {
console.log(err)
}
setLoading(false)
}
fetchAPI()
}, [])
if (loading === true) {
return <div>Loading...</div>
}
}
export default APICall
This is another child component I have a problem with:
import {useState, useEffect} from "react"
function Filter({datum}) {
const [filterText, setFilterText] = useState("")
const handleInput = (e) => {
setFilterText(e.target.value)
}
useEffect(() => {
const handleFilter = async () => {
try {
const filtered = await datum.title?.filter(item => {
return Object.keys(item).some(key =>
item[key].toString().toLowerCase().includes(filterText.toLocaleLowerCase())
)
})
console.log(filtered)
}
catch (err) {
console.log(err)
}
}
handleFilter()
}, [filterText])
return (
<div>
<input type="text" placeholder="Please enter the name of the the food" onChange={handleInput} value={filterText} />
{datum.map((el, id) => (
<div key={id} className="items">
<img src={el.image} alt={el.title} />
<p>{el.title}</p>
</div>
))}
</div>
)
}
export default Filter
Edited This is the some data I get from API:
0: {id: 201369, title: 'Yoplait Yogurt, Lactose Free Yogurt, Peach, 6.0 oz', image: 'https://spoonacular.com/productImages/201369-312x231.jpeg', imageType: 'jpeg'}
1: {id: 415304, title: 'Noosa Key Lime Finest Yoghurt, 8 OZ', image: 'https://spoonacular.com/productImages/415304-312x231.jpeg', imageType: 'jpeg'}
2: {id: 89557, title: 'Yoplait Light Yogurt with Granola, Blueberry, Fat Free, 12 oz, 2 Cups', image: 'https://spoonacular.com/productImages/89557-312x231.png', imageType: 'png'}
CodePudding user response:
I had to make a lot of changes for this to work. Some notes: you wanted to filter by the title, so you did datum.title.filter
datum is an array, it doesn't have the title property, that's why you are getting undefined. You had to use datum.filter
, and filter
function is not a promise, you don't need to use await
. I also created another state for the filtered, and let the original data untouched, to be able to reset the itens to the original items, otherwise if you filtered the original itens, they would be lost. I made the code in Filter.js
more simple, no need of useEffect, when you type you change the state of filteredDatum and the list get filtered, i also a nice search logic, to search for separate words, for example if the title is "yougurt lactose 100", if the search is "yog 100" it will find
App.js
import "../App.css";
import APICall from "./APICall.jsx";
import Filter from "./Filter.jsx";
import { useState } from "react";
function App() {
const [datum, setDatum] = useState([]);
const [datumFiltered, setDatumFiltered] = useState([]);
return (
<div className="App">
<Filter datum={datum} datumFiltered={datumFiltered} setDatumFiltered={setDatumFiltered} />
<APICall setDatum={setDatum} setDatumFiltered={setDatumFiltered} />
</div>
);
}
export default App;
Filter.js
function Filter({ datum, datumFiltered, setDatumFiltered }) {
const handleFilter = (e) => {
const filterText = e.target.value.toLowerCase();
const wordsToFilter = filterText.split(" ");
datumFiltered = datum.filter((item) => {
return wordsToFilter.every((word) => item.title.toLowerCase().includes(word));
});
setDatumFiltered(datumFiltered);
};
return (
<div>
<input type="text" placeholder="Please enter the name of the the food" onChange={handleFilter} />
{datumFiltered.map((el, id) => (
<div key={id} className="items">
<img src={el.image} alt={el.title} />
<p>{el.title}</p>
</div>
))}
</div>
);
}
export default Filter;
APICall.js
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
//REMOVE THIS FUNCTION
const axiosDummyCall = async () => {
return [
{
id: 201369,
title: "Yoplait Yogurt, Lactose Free Yogurt, Peach, 6.0 oz",
image: "https://spoonacular.com/productImages/201369-312x231.jpeg",
imageType: "jpeg",
},
{
id: 415304,
title: "Noosa Key Lime Finest Yoghurt, 8 OZ",
image: "https://spoonacular.com/productImages/415304-312x231.jpeg",
imageType: "jpeg",
},
{
id: 89557,
title: "Yoplait Light Yogurt with Granola, Blueberry, Fat Free, 12 oz, 2 Cups",
image: "https://spoonacular.com/productImages/89557-312x231.png",
imageType: "png",
},
];
};
function APICall({ setDatum, setDatumFiltered }) {
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchAPI = async () => {
setLoading(true);
try {
let datum = await axiosDummyCall(); //REPLACE WITH REAL CALL
setDatum(datum);
setDatumFiltered(datum);
} catch (err) {
console.log(err);
}
setLoading(false);
};
fetchAPI();
}, []);
if (loading === true) {
return <div>Loading...</div>;
}
}
export default APICall;