React App crashes when fetching searchresults from django API - reactjs

My React App is crashing when fetching searchResults from API, I have checked the API urls wise search queries and it works perfectly however when i try to send input via React and display results it crashes and even freezes my PC. I dont understand whats going on here. I have fetched results from the API in React without search query and it works. So the API works when used via Curl and React app can fetch and display all the data but unable to display specific data. Below is my code:
function Search() {
const [data, setData] = React.useState([]);
const [searchTerm, setSearchTerm] = React.useState("");
const handleChange = e => {
setSearchTerm(e.target.value);
};
React.useEffect(() => {
if (searchTerm) {
getData(searchTerm);
}
});
const getData = (searchTerm) => {
axios.get("http://localhost:8000/SearchPost/?search="+searchTerm)
.then(res => (setData(res.data)))
}
return (
<div className="App">
<input
type="text"
placeholder="Search"
value={searchTerm}
onChange={handleChange}
/>
<ul>
{data.map(item => (
<li>{item.co_N}</li>
))}
</ul>
</div>
);
}
export default Search;

One solution is to "debounce" setting searchTerm to minimize the request to the API:
we're going to use lodash package particularly it's debounce method (doc here), and useCallback from Hooks API (doc here) :
import React, { useState, useCallback, useRef } from "react";
import _ from "lodash";
import axios from "axios";
import TextField from "#material-ui/core/TextField";
const SearchInputComponent = ({ label }) => {
const [value, setValue] = useState("");
const [data, setData] = useState([]);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
getData(value);
}, 500), // you can set a higher value if you want
[]
);
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
export default SearchInputComponent;
with this code the front end will wait 500 ms before fetching api with the search input value.
here a sandBox example.
Possible Feature: Make search field generic
If in the future you will need a search component you can make it generic with Context:
first create a context file named for example SearchInputContext.js and add:
SearchInputContext.js
import React, {
createContext,
useState
} from 'react';
export const SearchInputContext = createContext({});
export const SearchInputContextProvider = ({ children }) => {
const [value, setValue] = useState('');
return (
<SearchInputContext.Provider
value={{ searchValue: value, setSearchValue: setValue }}
>
{children}
</SearchInputContext.Provider>
);
};
Next create a generic searchField component named for example SearchInput.js and add in it :
SearchInput.js
import React, {
useState,
useCallback,
useRef,
useContext
} from 'react';
import _ from 'lodash';
import TextField from "#material-ui/core/TextField";
import { SearchInputContext } from './SearchInputContext';
const SearchInputComponent = () => {
const [value, setValue] = useState('');
const { setSearchValue } = useContext(SearchInputContext);
const inputRef = useRef(null);
const debounceLoadData = useCallback(
_.debounce((value) => {
setSearchValue(value);
}, 500),
[]
);
const handleSearchFieldChange = (event) => {
const { value } = event.target;
setValue(value);
debounceLoadData(value);
};
return (
<>
<TextField
inputRef={inputRef}
id="searchField"
value={value}
label={"search"}
onChange={handleSearchFieldChange}
/>
</>
);
};
export default SearchInputComponent;
After in your App.js (or other component page where you want a searchField) add your ContextProvider like this:
App.js
import {ListPage} from "./searchPage";
import {SearchInputContextProvider} from './SearchInputContext';
import "./styles.css";
export default function App() {
return (
<SearchInputContextProvider>
<ListPage/>
</SearchInputContextProvider>
);
}
And finally add your searchComponent where you need a search feature like in the ListPage component :
SearchPage.js:
import React, { useState,useContext, useEffect } from "react";
import axios from "axios";
import SearchInputComponent from './SearchInput';
import {SearchInputContext} from './SearchInputContext'
export const ListPage = () => {
const [data, setData] = useState([]);
const { searchValue } = useContext(SearchInputContext);
useEffect(() => {
if(searchValue){
const getData = (name) => {
axios.get(`https://restcountries.eu/rest/v2/name/${name}`).then((res) => {
console.log(res);
setData(res.data);
});
};
return getData(searchValue)
}
}, [ searchValue]);
return (
<>
<SearchInputComponent />
{data &&
<ul>
{data.map(country=> (
<li key={country.alpha3Code}>{country.name}</li>
))
}
</ul>
}
</>
);
};
here a sandbox link of this example

Related

Having trouble getting a custom hook to work with Redux Toolkit

As you can tell from the code, I'm trying to make a custom hook that would sort the rendered cards.
When the cards first render, they show up in the default order from the API, but they get sorted only after forcing a re-render, usually by pressing Ctrl+S to save so Vite registers a change.
I'm new to custom hooks so I don't really know what I'm doing wrong, probably a problem with the dispatches inside the useEffect? How can I get the cards to sort properly?
This is the custom hook:
import { useEffect, useState } from 'react';
// Redux
import { getAllBosses, SortBosses } from '../redux/slices/Bosses';
import { useDispatch, useSelector } from 'react-redux';
export default function useFetch(value) {
const dispatch = useDispatch();
const { list: bosses } = useSelector(state => state.bosses);
const [type, setType] = useState([]);
useEffect(() => {
value === 'default'
? dispatch(getAllBosses())
: dispatch(SortBosses(value));
setType(bosses);
}, [value]);
return type;
}
And this is the component:
import { useEffect, useState } from 'react';
// Redux
// import { getAllBosses } from '../../redux/slices/Bosses';
// import { useDispatch, useSelector } from 'react-redux';
// components
import { BossCard, Loading, Pagination, Error } from '#components';
import { useFetch } from '../../hooks';
import { Cards } from '#components/styles/Cards.style';
function BossesList() {
// const dispatch = useDispatch();
const bosses = useFetch('ztoa');
const [currentPage, setCurrentPage] = useState(1);
const bossPerPage = 8;
const indexOfLastBoss = currentPage * bossPerPage;
const indexOfFirstBoss = indexOfLastBoss - bossPerPage;
const currentBosses = bosses.slice(indexOfFirstBoss, indexOfLastBoss);
const pages = pageNumber => {
setCurrentPage(pageNumber);
};
// useEffect(() => {
// dispatch(getAllBosses());
// }, [dispatch]);
useEffect(() => {
setCurrentPage(1);
}, [bosses]);
return (
<>
<Cards>
{currentBosses.length !== 0 ? (
currentBosses.map((boss, index) =>
boss.error ? (
<Error key={index} error={'No boss with that name.'} />
) : (
<BossCard
key={boss.id}
id={boss.id}
name={boss.name}
image={boss.image}
/>
)
)
) : (
<div>
<Loading />
</div>
)}
</Cards>
<Pagination
bossPerPage={bossPerPage}
bosses={bosses.length}
currentBosses={currentBosses}
pages={pages}
currentPage={currentPage}
/>
</>
);
}
export default BossesList;

Passing useState data from one functional component to another

I have two function components with useState in two different files in my project. I want to display the url on my FaceRecognition component if I set fetchSuccess to true.
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const [fetchSuccess, setFetchSuccess] = useState(false);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
};
return (
<div>
// I return a form that allowed me to make the fetch call
</div>
);
};
export default ImageLinkForm;
const FaceRecognition = () => {
return (
<div>
{/* if fetchSuccess */}
<img src=url />
</div>
);
};
export default FaceRecognition;
This really depends on how these components are hierarchically related but one easy-ish option is to use the context API
// context/image.js
import { createContext, useState } from "react";
export const ImageContext = createContext({ fetchSuccess: false });
export const ImageContextProvider = ({ children }) => {
const [fetchSuccess, setFetchSuccess] = useState(false);
const setSuccessful = () => {
setFetchSuccess(true);
};
return (
<ImageContext.Provider value={{ fetchSuccess, setSuccessful }}>
{children}
</ImageContext.Provider>
);
};
Your components can then use the context to read the value...
import { useContext } from "react";
import { ImageContext } from "path/to/context/image";
const FaceRecognition = () => {
const { fetchSuccess } = useContext(ImageContext);
return <div>{fetchSuccess && <img src="url" />}</div>;
};
and write the value...
import { useContext, useState } from "react";
import { ImageContext } from "path/to/context/image";
const ImageLinkForm = () => {
const [url, setUrl] = useState("");
const { setSuccessful } = useContext(ImageContext);
const onInputChange = (event) => {
// I get the url and fetchSuccess is true
setSuccessful();
};
return (
<div>{/* I return a form that allowed me to make the fetch call */}</div>
);
};
The only thing you need to do is wrap both these components somewhere in the hierarchy with the provider
import { ImageContextProvider } from "path/to/context/image";
const SomeParent = () => (
<ImageContextProvider>
<ImageLinkForm />
<FaceRecognition />
</ImageContextProvider>
);

React custom hook state not 'always there'

I thought had a better grasp of hooks but I've clearly got something wrong here. Not all of the character objects will have what I'm trying to get but it wont work with those that do.
I cna't even build in a check for character.comics.available. Same errors appear. I'm presuming I'm getting them before the state is set? But {character.name} always works.
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
// from the Route path="/character/:id"
const { id } = useParams();
// custom hook. useCharacter.js
const [character] = useCharacter(id);
// this only works sometimes but errors if i refresh the page
// console.log(character.comics.available);
return (
<div>
<h2 className="ui header">Character Details</h2>
<p>Works every time: {character.name}</p>
<div className="ui segment"></div>
<pre></pre>
</div>
);
};
export default CharacterDetail;
Custom hook useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
const useCharacter = (id) => {
const [character, setCharacter] = useState({});
useEffect(() => {
loadItem();
return () => {};
}, [id]);
const loadItem = async (term) => {
const response = await marvel.get(`/characters/${id}`);
console.log(response.data.data.results[0]);
setCharacter(response.data.data.results[0]);
};
return [character];
};
export default useCharacter;
error when console is uncommented
Uncaught TypeError: Cannot read property 'available' of undefined
at CharacterDetail (CharacterDetail.js:11)
...
Here is the character object.
thanks to #Nikita for the pointers. Settled on this...
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
const { id } = useParams();
// custom hook. useCharacter.js
const { isLoading, character } = useCharacter(id);
const isArray = character instanceof Array;
if (!isLoading && isArray === false) {
console.log("isLoading", isArray);
const thumb =
character.thumbnail.path +
"/portrait_uncanny." +
character.thumbnail.extension;
return (
<div>
<h2 className="ui header">{character.name}</h2>
<img src={thumb} />
<div className="ui segment">{character.comics.available}</div>
<div className="ui segment">{character.series.available}</div>
<div className="ui segment">{character.stories.available}</div>
</div>
);
}
return <div>Loading...</div>;
};
export default CharacterDetail;
useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
function useCharacter(id) {
const [character, setCharacter] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
setIsLoading(true);
await marvel
.get(`/characters/${id}`)
.then((response) => {
/* DO STUFF WHEN THE CALLS SUCCEEDS */
setIsLoading(false);
setCharacter(response.data.data.results[0]);
})
.catch((e) => {
/* HANDLE THE ERROR (e) */
});
};
fetchData();
}, [id]);
return {
isLoading,
character,
};
}
export default useCharacter;

REACT Context Maximum update depth exceeded

I have trouble with react context.
Especially with function getProductCategory which I use in the second component.
My React context provider looks like :
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
});
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); //this line is causing the problem
console.log(productFromCategory);
return productsCategory;
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
I want to get access to my context from another component Mats which is a page.
import React, { useContext, useEffect } from "react";
import { ProductContext } from "../../context";
/accesoriesComponent/CategoryTitle/CategoryTitle";
import Filters from "../../components/accesoriesComponent/Filters/Filters";
import ProductWrapper from "../../components/accesoriesComponent/ProductWrapper/ProductWrapper";
export default function Mats(props) {
const { getProductCategory } = useContext(ProductContext);
const mats = getProductCategory("mats");
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{mats && <ProductWrapper products={mats} />}
</div>
</div>
);
}
When I want to visit page Mats, I get error "Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside component WillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.".
I marked the line of code in the first code, that courses the problem .
I don't know how to handle it.
Any suggestions ?
Based on your code:-
instead of return-ing productsCategory from your getProductCategory function. Send your productsCategory as ProductContext.Provider values props
Btw, your useEffect should have [] or empty array as dependency
ProductContextProvider.js:-
import React, { createContext, useState, useEffect } from "react";
import data from "./data";
export const ProductContext = createContext();
const ProductContextProvider = ({ children }) => {
const [productsCategory, setProductCategory] = useState();
const [products, setProducts] = useState();
useEffect(() => {
setProducts(data);
}, []); // making sure it will only run once when rendered
function getProductCategory(category) {
const productFromCategory = data.filter(
(product) => product.type === category
);
setProductCategory(productFromCategory); // you already set the data in here
console.log(productFromCategory);
// return productsCategory; // no need to return this
}
const getProduct = (product) => {
const currentProduct = products
? products.filter((item) => item.slug === product)[0]
: undefined;
return currentProduct;
};
return (
<ProductContext.Provider
value={{ productsCategory, getProduct, getProductCategory }}
>
{children}
</ProductContext.Provider>
);
};
export default ProductContextProvider;
So in Mats.js, just get display your updated ``
export default function Mats(props) {
const { productsCategory, getProductCategory } = useContext(ProductContext);
// Shouldn't do this. Cause it will keep rerender
// const mats = getProductCategory("mats");
// default state of category
const [category, setCategory] = useState('mats')
// use useffect to send the updated products by category
useEffect(() => {
(() => {
if(category) {
getProductCategory(category);
}
})()
}, [category]) // will initiated every time category change
return (
<div>
<div style={{ display: "flex" }}>
<Filters />
{productsCategory && <ProductWrapper products={productsCategory} />}
</div>
</div>
);
}

How do I properly set up an API call using useEffect?

Here is my entire component. In the console the correct data is showing up at "data" but when I try to run map on it it says "map is not a function." The 16 items in the console are the correct beaches.
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export default function Beaches() {
const [data, setData] = useState({beaches: []})
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData(result.data);}
fetchBeaches();
}, [])
console.log(data)
return (
<ul>
{data.beaches.map(beach => (
<button>{beach.name}</button>
))}
</ul>
)
}
Because you're not setting the beaches data in state correctly.
Replace useEffect code with this:
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios('http://localhost:3000/beaches');
setData({beaches: result.data});
}
fetchBeaches();
}, [])
furthermore, you can improve the state structure of beaches data:
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function Beaches() {
const [beaches, setBeaches] = useState([]);
// const [hasError, setErrors] = useState(false)
useEffect(() => {
const fetchBeaches = async () => {
const result = await axios("http://localhost:3000/beaches");
setBeaches(result.data);
};
fetchBeaches();
}, []);
return (
<ul>
{beaches.map((beach) => (
<button>{beach.name}</button>
))}
</ul>
);
}

Resources