How can I make a search box in react-hooks? - json

I'm making a cocktail recipe web. If I search for the name of the cocktail, I want the cocktail to appear. The error message shown to me is as follows.
"TypeError: Cannot read property 'filter' of undefined"
Please tell me how to solve this problem. I'm a beginner. Is there a problem with my code?
This is Search.jsx
import React, { useState, useEffect } from "react";
import useFetch from "../Components/useFetch";
const Searchs = () => {
const url =
"https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita";
const [data] = useFetch(url);
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const handleChange = (event) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results = data.drinks.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm]);
return (
<Wrapper>
<Search
type="text"
placeholder="재료 또는 이름을 검색하세요"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{searchResults.map((item) => (
<li>{item}</li>
))}
</ul>
</Wrapper>
);
};
export default Searchs;
This is useFetch.jsx
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
}
useEffect(() => {
fetchUrl();
}, []);
return [data, loading];
}
export default useFetch;
This is JSON
{
"drinks": [
{
"idDrink": "12784",
"strDrink": "Thai Iced Coffee",
"strCategory": "Coffee / Tea",
"strIBA": null,
"strAlcoholic": "Non alcoholic",
"strGlass": "Highball glass",
"strDrinkThumb": "https://www.thecocktaildb.com/images/media/drink/rqpypv1441245650.jpg",
"strIngredient1": "Coffee",
"strIngredient2": "Sugar",
"strIngredient3": "Cream",
"strIngredient4": "Cardamom",
"strMeasure1": "black",
"strMeasure3": " pods\n",
"strImageAttribution": null,
"strCreativeCommonsConfirmed": "No",
"dateModified": "2015-09-03 03:00:50"
}
]
}

Do null check before filter(), Your API might return null/undefined you should handle such cases.
Bonus: onChange={handleChange} don't directly call API on change, add some denounce check, to improve performance.
useEffect(() => {
const results = data?.drinks?.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
) ?? [];
setSearchResults(results);
}, [searchTerm]);

you did many mistakes in this code look below how I did it
here you can find sandbox URL where you can see live working code
https://codesandbox.io/s/boring-tesla-hoc11?file=/src/App.js:75-1141
I have changed your wrapper to input element for testing you can revert it back
const Searchs = () => {
const url =
"https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita";
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const handleChange = (event) => {
setSearchTerm(event.target.value);
};
useEffect(() => {
async function fetchUrl() {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
const results = data.drinks.filter(({ strDrink }) =>
strDrink.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}
fetchUrl();
}, [searchTerm]);
return (
<>
<input
type="text"
placeholder="재료 또는 이름을 검색하세요"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{searchResults.map((item,index) => (
<li key={index}>{item.strDrink}</li>
))}
</ul>
</>
);
};

It seems like your API is returning nothing. You should add a check to see if anything is returned from API:
ALSO: you have to include data which you get from useFetch to the useEffect dependencies, otherwise it's value won't be changed in each useEffect call:
useEffect(() => {
const results = data?.drinks?.filter(({ strDrink }) =>
data.strDrink.toLowerCase().includes(searchTerm)
) ?? [];
setSearchResults(results);
}, [searchTerm, data]);

Related

Having trouble rendering items in React.js

I'm pretty new to React, being very used to OOP this is kind of twisting my brain a bit!
I'm playing around with the PokeAPI, to show different stats of pokemon, I've gotten all my data into the array fine, I've just hit a brick wall when it comes to rendering it, nothing apears.
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import PokemonRender from './PokemonRender';
ReactDOM.render(
<React.StrictMode>
<PokemonRender />
</React.StrictMode>,
document.getElementById('root')
);
PokemonRender:
import React, { useEffect } from 'react';
import axios from 'axios';
const PokemonRender = () => {
const pokemonList = [];
const getPokemonData = async (id) => {
try{
const dataArray = [];
const url = 'https://pokeapi.co/api/v2/pokemon/' + id;
const res = await axios.get(url);
dataArray.push(res.data);
pokemonList.push(dataArray[0]);
dataArray.length = 0;
} catch(e) {
console.log(e);
}
}
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
)
useEffect((i) => {
for(i = 1; i < 152; i++) {
getPokemonData(i);
} }, []);
return (
pokemonList.map((pokemon) => (
<Pokemon id={pokemon.id} name={pokemon.name} />
))
)
}
export default PokemonRender;
edit:
I've boiled it down to this, the arrays all contain the right data (which they didn't before) but the return statement isn't returning what it should? Any ideas?
import React, { useEffect, useState } from 'react';
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
);
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const tempPokemonArray = []
const getPokemonData = async (id) => {
try {
fetch("https://pokeapi.co/api/v2/pokemon/" + id).then(res => res.json()).then((pokemon) => tempPokemonArray.push(pokemon))
} catch (e) {
console.log(e);
}
};
useEffect((i) => {
for (i = 1; i < 5; i++) {
getPokemonData(i)
}
setPokemonList(tempPokemonArray);
tempPokemonArray.length = 0;
}, []);
return pokemonList.map((pokemon) => <Pokemon id={pokemon.id} name={pokemon.name}/>);
};
export default PokemonRender;
You need to use the state to handle data inside the component. You can do it by following this link.
By the way, you should not getPokemonData in the loop like that. It will re-render your component 151 times.
You need to call useState to make a re-rendering. Without it, React won't understand when to re-render your UI. In your case, you need to update pokemon list with useState
Did a small refactoring for your code which are removing dataArray, fixing useEffect, and moving your Pokemon component outside of PokemonRender to avoid calling it many times
import React, { useEffect, useState } from "react";
import axios from "axios";
const Pokemon = ({ id, name }) => (
<div>
<p>{id}</p>
<p>{name}</p>
</div>
);
const PokemonRender = () => {
const [pokemonList, setPokemonList] = useState([]);
const getPokemonData = async (id) => {
try {
const url = "https://pokeapi.co/api/v2/pokemon/" + id;
const res = await axios.get(url);
//pokemonList.push(res.data);
setPokemonList([...pokemonList, res.data]); //add current data to the pokemon list
} catch (e) {
console.log(e);
}
};
useEffect(() => {
const blankArray = new Array(152);
for (const i = 1; blankArray.length < 152; i++) {
getPokemonData(i);
}
}, []);
return pokemonList.map((pokemon) => (
<Pokemon id={pokemon.id} name={pokemon.name} />
));
};
export default PokemonRender;
useState is also asynchronous, so I think you keep updating data and UI at the same time which will make your data unsafe (maybe lost during re-rendering). So you should return data in a new array and then render all of them together. For example
const getPokemonData = async (id) => {
try{
const url = 'https://pokeapi.co/api/v2/pokemon/' + id;
const res = await axios.get(url);
dataArray.push(res.data);
return res.data
} catch(e) {
console.log(e);
}
}
Update useEffect with a list of pokemon
useEffect(() => {
const blankArray = new Array(152)
for(const i = 1; blankArray.length < 152; i++) {
pokemonListData.push(getPokemonData(i));
}
setPokemonList(pokemonListData) //only run once
}, []);
Since you're new to these stuff, I'd suggest you follow up this document

React Native Download JSON but not displaying in Flatlist

I am trying to download the JSON. React Native is downloading the json but I am not sure why Flatlist is not displaying the items. If I change the data={dummyData} in flatlist to data={MyList} then, the flatlist is able to display.
let viewableItemsChanged = null;
const dummyData = GrabData('http://hunterdata.serveblog.net/10record.json');
const MyList = [
{"id":"0","title":"MyBook0","url":"URLBook-0","image":"image-0" },
{"id":"1","title":"MyBook1","url":"URLBook-1","image":"image-1" },
{"id":"2","title":"MyBook2","url":"URLBook-2","image":"image-2" },
{"id":"3","title":"MyBook3","url":"URLBook-3","image":"image-3" },
{"id":"4","title":"MyBook4","url":"URLBook-4","image":"image-4" },
{"id":"5","title":"MyBook5","url":"URLBook-5","image":"image-5" }];
async function GrabData(TheURL) {
let abc = [];
try {
let response = await fetch(TheURL, {headers: {'Cache-Control' : 'no-cache'}});
let responseJson = await response.json();
console.log(responseJson);
return responseJson;
} catch (error) {
console.error(error);
}
}
const renderItem = ({item}) => {
return <View><Text>{item.title}</Text></View>
}
const List = () => {
return (
<FlatList
style={styles.list}
data={dummyData}
renderItem={renderItem}
/>
)
};
there is an issue with your code. you are calling the async function without await keyword. so it returns undefine response like this. {"_U": 0, "_V": 0, "_W": null, "_X": null}
Please Try this solution.
import React, { useEffect , useState } from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const [data, setData] = useState([])
useEffect(() => {
apicall();
},[])
const apicall = async () => {
let dd = await GrabData("http://hunterdata.serveblog.net/10record.json");
setData(dd)
}
const GrabData = async (TheURL) => {
try {
let response = await fetch(TheURL, {headers: {'Cache-Control' : 'no-cache'}});
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error(error);
}
}
const renderItem = ({ item }) => (
<Item title={item?.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item?.id}
/>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});
export default App;
You should activate the functions in componentDidMount also try states instead of const

Change number of servings on click (React Hooks - API)

I'm working on a recipe site using API from a third party and want to change the number of servings (which is output from the API data) when clicking the + & - button. I tried assigning the output serving amount <Servings>{recipe.servings}</Servings> in a variable and useState to update it but it kept showing errors. I would appreciate any help (preferably using react Hooks). Thanks :)
Here is my code:
const id = 716429;
const apiURL = `https://api.spoonacular.com/recipes/${id}/information`;
const apiKey = "34ac49879bd04719b7a984caaa4006b4";
const imgURL = `https://spoonacular.com/cdn/ingredients_100x100/`;
const {
data: recipe,
error,
isLoading,
} = useFetch(apiURL + "?apiKey=" + apiKey);
const [isChecked, setIsChecked] = useState(true);
const handleChange = () => {
setIsChecked(!isChecked);
};
return (
<Section>
<h2>Ingredients</h2>
<ServingsandUnits>
{recipe && (
<ServingsIncrementer>
<p>Servings: </p>
<Minus />
<Servings>{recipe.servings}</Servings>
<Plus />
</ServingsIncrementer>
)}
<ButtonGroup>
<input
type="checkbox"
id="metric"
name="unit"
checked={isChecked}
onChange={handleChange}
/>
<label htmlFor="male">Metric</label>
</ButtonGroup>
</ServingsandUnits>
</Section>
};
My custom hook is called useFetch:
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (!res.ok) {
// error coming back from server
throw Error("Could not fetch the data for that resource");
}
return res.json();
})
.then((data) => {
setIsLoading(false);
setData(data);
setError(null);
})
.catch((err) => {
if (err.name === "AbortError") {
console.log("Fetch aborted");
} else {
// auto catches network / connection error
setIsLoading(false);
setError(err.message);
}
});
return () => {
abortCont.abort();
};
}, [url]);
return { data, isLoading, error };
};
export default useFetch;

How to avoid flash before entering next route with loading in react router?

I would like to use prepare to prepare next page before entering route and at this point a loading page is shown.
I have tried to use useLocation and useEffect but next page will flash in before loading page is shown.
const [loading, setLoading] = useState(true);
const location = useLocation(); // react-router
useEffect(() => {
setLoading(true);
setTimeout(() => {
prepare(() => setLoading(false)); // callback when `prepare` is complete
}, 0);
}, [location.pathname, prepare, setLoading]);
return (
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
render={() => loading ? '' : <route.component />}
/>
))}
</Switch>
);
Currently I am using a dirty solution DelayedComponent
const DelayedComponent = (props) => {
const { component } = props;
const [show, setShow] = useState(false);
useEffect(() => {
const timeout = setTimeout(() => {
setShow(true);
}, 1000);
return () => {
clearTimeout(timeout);
}
}, []);
return show ? createElement(component) : <div />;
};
I am not sure, that i understood your problem correctly, but i will try to suggest something
It's your router:
<Switch>
{routes.map(({ path, component: Component }) => (
<Route
exact
key={path}
path={path}
>
<Component />
</Route>
))}
</Switch>
then your any component
// Users.js
import React from 'react';
import useFetchUsers from './useFetchUsers';
const Users = () => {
const { isLoadingUsers, users } = useFetchUsers();
if (isLoadingUsers) {
return <LoadingWhatEverComponent />
}
return (
<div>{users.map((user) => user.name)}</div>
)
}
Before users are not loaded you will show loading screen
// useFetchUsers.js
import { useCallback, useState, useEffect } from 'react';
export default () => {
const [isLoading, setIsLoading] = useState(true);
const [users, setUsers] = useState([]);
const fetchUsers = useCallback(async () => {
try {
const response = await axios(...);
setUsers(response.data.users);
} catch (error) {
console.error(error);
setUsers([]);
} finally {
setLoading(false);
}
}, [])
useEffect(() => {
fetchUsers();
}, [])
return {
isLoadingUsers: isLoading,
users
}
}
as a result, before fetchUsers is not over, you can see only loading component

access the data of a hook- React

I am requesting this API, then I save the result in the "data" hook. when I want to print "data.total_results" everything ok but when I want to print a key that has more data inside it doesn't leave me "Data.results [0] .title"
import React, { useState, useEffect } from 'react';
const Movie = () => {
// Declara una nueva variable de estado, que llamaremos "count".
const [Data, setData] = useState("");
useEffect(() => {
const Cargar = async () => {
let respuesta = await fetch("https://api.themoviedb.org/3/discover/movie?api_key=ce322f54257cc9286282b320c5e9b2a0&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1");
let respuestaJSON = await respuesta.json();
setData(respuestaJSON);
};
Cargar();
}, [Data]);
return (
<div>
{Data.total_results}
{/* {Data.results[0].title} */}
</div>
);
}
export default Movie;
You need to check if the results property is defined.
<div className="App">
<h1>Total {Data.total_results}</h1>
{typeof Data.results !== "undefined" &&
Data.results.map((movie, index) => {
return <h3>{movie.title}</h3>;
})}
</div>
Working Demo: https://codesandbox.io/s/distracted-feather-4l55r
A few days back, I have created a repository and implemented OMDB API with React. This may help you.
https://github.com/jogeshpi03/omdb-react
Your code is almost right. There's a bug and a couple improvements you can make.
First let's take a look at the useEffect hook.
useEffect takes two arguments: a function, and a list of dependencies. The first time the component is rendered, and when any of the dependencies change, the function argument will be executed again.
So right now you have a bug with your dependencies that is causing an infinite loop. When the component is rendered, you execute the function in useEffect. That function will eventually set Data. When the value for Data is set, that will cause the useEffect function to be executed again. Which will set a new value to Data, which will execute the function again, ad infinitum.
The fix is to set the dependencies to [] if you only want that to run once. Like this:
useEffect(() => {
const Cargar = async () => {
let respuesta = await fetch("https://api.themoviedb.org/3/discover/movie?api_key=ce322f54257cc9286282b320c5e9b2a0&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1");
let respuestaJSON = await respuesta.json();
setData(respuestaJSON);
};
Cargar();
}, []); // <-------- This changed to []
I hope that fixes your issue.
Now I will suggest some improvements:
I would set the initial state of Data to undefined
const [Data, setData] = useState();
and then use a conditional in the JSX like so
if (!Data) {
return <div>Loading...</div>
}
return (
<div>
{Data.total_results}
</div>
);
const [Data, setData] = useState([]);
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import ReactTable from 'react-table'
import 'react-table/react-table.css'
const Movie = () => {
// Declara una nueva variable de estado, que llamaremos "count".
const [Data, setData] = useState([]);
useEffect(() => {
axios.get("https://api.themoviedb.org/3/discover/movie?api_key=ce322f54257cc9286282b320c5e9b2a0&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1")
.then(function(response) {
console.log(response.data.results)
setData(response.data.results);
}).catch(function(error) {
console.log(error);
})
}, []);
return (<div>
{Data.map(function (row, i) {
return <div key={i}>
{row.title}
</div>
})}
</div>)
}
export default Movie;