How to set initial state in useState dependent on another state - javascript

There are products coming from API (in the state 'productsList') and I am working on building an input range slider that will have the initial value set to the value of the costly price.
So, all I want is to use this line:
const [maxPriceInSlider, setMaxPriceInSlider] = useState(maxPricedProductPrice);
maxPricedProductPrice comes from the highest value in the productsList state.
but it is not working.
any help is appreciated.
The complete code for the component is:
import React from "react";
import { useEffect, useState } from "react";
import axios from "axios";
import { ColorRing as Loader } from "react-loader-spinner";
import HeroGeneral from "../components/UI/HeroGeneral";
import BidItem from "../components/General/BidItem";
import "./bidslist.css";
const BidsList = () => {
const [loading, setLoading] = useState(false);
const [productsList, setProductsList] = useState([]);
// get maximum priced product price
const maxPricedProductPrice = Math.max(...productsList.map((prod) => prod.basePrice));
const [minPriceInSlider, setMinPriceInSlider] = useState(0);
// --------------------------------------------------------------------------------------
// --------------- I WANT the maxPricedProductPrice to be set as initial state of maxPriceInSlider like this:
// const [maxPriceInSlider, setMaxPriceInSlider] = useState(maxPricedProductPrice);
// But it is not working
// productsList is coming from API
const [maxPriceInSlider, setMaxPriceInSlider] = useState(0);
const onMinPriceSliderChangeHandler = (e) => {
setMinPriceInSlider(e.target.value);
};
const onMaxPriceSliderChangeHandler = (e) => {
setMaxPriceInSlider(e.target.value);
};
// fetch all the prodcuts AFTER 1st page render
useEffect(() => {
// scroll to top on mount
window.scrollTo(0, 0);
setLoading(true);
axios
.get("http://localhost:5000/api/v1/products")
.then((data) => {
setProductsList(data.data.data.products);
setLoading(false);
})
.catch((err) => {
setLoading(false);
});
}, []);
return (
<div>
<HeroGeneral />
<div className="bidlist_page_content">
<aside className="filter">
<h2>Filter by</h2>
<div className="filterby_cat">
<h3>Category: </h3>
<select name="c" id="">
<option value="Electronics">Electronics</option>
<option value="Others">Others</option>
</select>
</div>
<div className="filterby_price">
<h3>
Start price:{" "}
<input
type="range"
name="minPriceInSlider"
value={minPriceInSlider}
min="0"
max={maxPricedProductPrice}
onChange={onMinPriceSliderChangeHandler}
/>
{minPriceInSlider}
</h3>
<h3>
End price:{" "}
<input
type="range"
name="maxPriceInSlider"
value={maxPriceInSlider}
min="0"
max={maxPricedProductPrice}
onChange={onMaxPriceSliderChangeHandler}
/>
{maxPriceInSlider}
</h3>
</div>
</aside>
<div>
<div className="divlist_grid_main">
{loading && (
<Loader
visible={true}
height="100"
width="100"
ariaLabel="blocks-loading"
wrapperStyle={{}}
wrapperClass="blocks-wrapper"
colors={["#212529"]}
/>
)}
{!loading && productsList.length === 0 && "No products found!"}
{productsList.map((prod) => (
<BidItem
key={prod._id}
description={prod.description}
category={prod.category}
name={prod.name}
closesAt={prod.closesAt}
imgURL={prod.imageURL}
prodId={prod._id}
bPrice={prod.basePrice}
/>
))}
</div>
</div>
</div>
</div>
);
};
export default BidsList;

Do you have any trouble with updating the maxPriceInSlider inside the useEffect like the following?
useEffect(() => {
// scroll to top on mount
window.scrollTo(0, 0);
setLoading(true);
axios
.get("http://localhost:5000/api/v1/products")
.then((data) => {
setProductsList(data.data.data.products);
const tempProducts = data.data.data.products;
const maxPricedProductPrice = Math.max(...tempProducts.map((prod) => prod.basePrice));
// update the max price here
setMaxPriceInSlider(maxPricedProductPrice);
setLoading(false);
})
.catch((err) => {
setLoading(false);
});
}, []);
If that doesn't work for you, you can hide the range input until the API call ends and once you have the max price then show that in the screen.

You can try a different approach.
After retrieving data from API and setting productsList also set maxPrieceInSlider at the same time like this:
axios
.get("http://localhost:5000/api/v1/products")
.then((data) => {
const maxPricedProductPrice = Math.max(...productsList.map((prod) => prod.basePrice));
setProductsList(data.data.data.products);
setMaxPriceInSlider(maxPricedProductPrice);
setLoading(false);
})
.catch((err) => {
setLoading(false);
});
this is much more efficient because if you keep getting the max price operation at the beginning of your code like you did, it will hinder your app because each time react re-renders your component, this operation will keep executing again and again needlessly.
let me if it works.

Related

How can I send the state (useState) of one file component to another file's component?

REACT.js:
Let say I have a home page with a search bar, and the search bar is a separate component file i'm calling.
The search bar file contains the useState, set to whatever the user selects. How do I pull that state from the search bar and give it to the original home page that
SearchBar is called in?
The SearchBar Code might look something like this..
import React, { useEffect, useState } from 'react'
import {DropdownButton, Dropdown} from 'react-bootstrap';
import axios from 'axios';
const StateSearch = () =>{
const [states, setStates] = useState([])
const [ stateChoice, setStateChoice] = useState("")
useEffect (()=>{
getStates();
},[])
const getStates = async () => {
let response = await axios.get('/states')
setStates(response.data)
}
const populateDropdown = () => {
return states.map((s)=>{
return (
<Dropdown.Item as="button" value={s.name}>{s.name}</Dropdown.Item>
)
})
}
const handleSubmit = (value) => {
setStateChoice(value);
}
return (
<div>
<DropdownButton
onClick={(e) => handleSubmit(e.target.value)}
id="state-dropdown-menu"
title="States"
>
{populateDropdown()}
</DropdownButton>
</div>
)
}
export default StateSearch;
and the home page looks like this
import React, { useContext, useState } from 'react'
import RenderJson from '../components/RenderJson';
import StateSearch from '../components/StateSearch';
import { AuthContext } from '../providers/AuthProvider';
const Home = () => {
const [stateChoice, setStateChoice] = useState('')
const auth = useContext(AuthContext)
console.log(stateChoice)
return(
<div>
<h1>Welcome!</h1>
<h2> Hey there! Glad to see you. Please login to save a route to your prefered locations, or use the finder below to search for your State</h2>
<StateSearch stateChoice={stateChoice} />
</div>
)
};
export default Home;
As you can see, these are two separate files, how do i send the selection the user makes on the search bar as props to the original home page? (or send the state, either one)
You just need to pass one callback into your child.
Homepage
<StateSearch stateChoice={stateChoice} sendSearchResult={value => {
// Your Selected value
}} />
Search bar
const StateSearch = ({ sendSearchResult }) => {
..... // Remaining Code
const handleSubmit = (value) => {
setStateChoice(value);
sendSearchResult(value);
}
You can lift the state up with function you pass via props.
const Home = () => {
const getChoice = (choice) => {
console.log(choice);
}
return <StateSearch stateChoice={stateChoice} giveChoice={getChoice} />
}
const StateSearch = (props) => {
const handleSubmit = (value) => {
props.giveChoice(value);
}
// Remaining code ...
}
Actually there is no need to have stateChoice state in StateSearch component if you are just sending the value up.
Hello and welcome to StackOverflow. I'd recommend using the below structure for an autocomplete search bar. There should be a stateless autocomplete UI component. It should be wrapped into a container that handles the search logic. And finally, pass the value to its parent when the user selects one.
// import { useState, useEffect } from 'react' --> with babel import
const { useState, useEffect } = React // --> with inline script tag
// Autocomplete.jsx
const Autocomplete = ({ onSearch, searchValue, onSelect, suggestionList }) => {
return (
<div>
<input
placeholder="Search!"
value={searchValue}
onChange={({target: { value }}) => onSearch(value)}
/>
<select
value="DEFAULT"
disabled={!suggestionList.length}
onChange={({target: {value}}) => onSelect(value)}
>
<option value="DEFAULT" disabled>Select!</option>
{suggestionList.map(({ id, value }) => (
<option key={id} value={value}>{value}</option>
))}
</select>
</div>
)
}
// SearchBarContainer.jsx
const SearchBarContainer = ({ onSelect }) => {
const [searchValue, setSearchValue] = useState('')
const [suggestionList, setSuggestionList] = useState([])
useEffect(() => {
if (searchValue) {
// some async logic that fetches suggestions based on the search value
setSuggestionList([
{ id: 1, value: `${searchValue} foo` },
{ id: 2, value: `${searchValue} bar` },
])
}
}, [searchValue, setSuggestionList])
return (
<Autocomplete
onSearch={setSearchValue}
searchValue={searchValue}
onSelect={onSelect}
suggestionList={suggestionList}
/>
)
}
// Home.jsx
const Home = ({ children }) => {
const [result, setResult] = useState('')
return (
<div>
<SearchBarContainer onSelect={setResult} />
result: {result}
</div>
)
}
ReactDOM.render(<Home />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Just pass a setState to component
parent component:
const [state, setState] = useState({
selectedItem: ''
})
<StateSearch state={state} setState={setState} />
change parent state from child component:
const StateSearch = ({ state, setState }) => {
const handleStateChange = (args) => setState({…state, selectedItem:args})
return (...
<button onClick={() => handleStateChange("myItem")}/>
...)
}

Dynamically create options from a dropdown select menu in react

So, I'm trying to dynamically create the options of a select dropdown, I make the fetch of an api with the states of my country, but I don't know how to access the content inside each object..
As you can see below, the data is being pulled from the API, that is, the fetch worked, but I don't know how to create the options that will be inside the Select with each object..
import { EmailIcon, LocationIcon } from './assets/FormSvgIcons'
import { useEffect, useState } from 'react';
const SettingsForm = () => {
const [stateList, setStateList] = useState([]);
const [userLocation, setUserLocation] = useState('');
const handleLocation = () => {
setUserLocation(e.target.value);
}
useEffect(() => {
let initialStates = [];
fetch('https://servicodados.ibge.gov.br/api/v1/localidades/estados/')
.then(response => {
return response.json();
}).then(data => {
initialStates = data.map((states) => {
return states
});
console.log(initialStates);
setStateList({states: initialStates});
});
}, []);
const createDropdownOptions = () => {
const createOptions = stateList.map((state, i) => {
Object.keys(state).map(singleState => (
<option value={i}>{singleState.sigla}</option>
))
});
return createOptions;
}
return (
<form>
<div className="user-country">
<label className="white-label">
Local
</label>
<div className="input-icon-wrapper">
<div className="icon-input w-embed">
<LocationIcon />
</div>
<select
className="select-field white-select w-select"
id="locationField"
name="locationField"
onChange={handleLocation}
>
{createDropdownOptions()}
</select>
</div>
</div>
</form>
)
I know that the error is in the createDropdownOptions function because it is responsible for creating the options, but I don't know how to do it, any light?
I see your problem, your logic is correct, but it is poorly implemented, once you have filtered the data, it is only rendering a new component:
import { EmailIcon, LocationIcon } from "./assets/FormSvgIcons";
import React, { useEffect, useState } from "react";
export default function SettingsForm() {
const [stateList, setStateList] = useState([]);
useEffect(() => {
fetch("https://servicodados.ibge.gov.br/api/v1/localidades/estados/")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
setStateList(data);
});
}, []);
return (
<form>
<div className="user-country">
<label className="white-label">Local</label>
<div className="input-icon-wrapper">
<div className="icon-input w-embed">
<LocationIcon />
</div>
<select
className="select-field white-select w-select"
id="locationField"
name="locationField"
onChange={handleLocation}
>
{stateList.map((state) => {
return <CreateDropdownOptions state={state} />;
})}
</select>
</div>
</div>
</form>
);
}
function CreateDropdownOptions({ state }) {
return (
<option key={state.id} value={state.sigla}>
{state.sigla}
</option>
);
}
I recommend using a component for each option, this will make it easier if you later need to do some action on the
First you could simplify your useEffect to the code below. As you are making a map where the callback returns the same object for each iteration, better you use data as it's, because the output would be the same.
useEffect(() => {
fetch("https://servicodados.ibge.gov.br/api/v1/localidades/estados/")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
setStateList(data);
});
}, []);
Then change createDropdownOptions to the code below. You can change the value or what's displayed to nome:
const createDropdownOptions = () => {
const createOptions = stateList.map((state) => (
<option key={state.id} value={state.sigla}>
{state.sigla}
</option>
));
return createOptions;
};
And finnaly you would need to pass the event to handleLocation:
const handleLocation = (e) => {
setUserLocation(e.target.value);
}
Don't overthink. Tips:
Keep your fetching logic as simple as possible.
Prefer Async Await instead of then chaining for readability.
Honor your state initialization. If you said it is an Array, don't set it as an object.
If you have an array, you can easily map it into jsx and generate your options.
You did very well, and got really close. Take a look at the changes I've done to get it working:
import { useEffect, useState } from 'react';
export const SettingsForm = () => {
const [stateList, setStateList] = useState([]);
const [userLocation, setUserLocation] = useState('');
const handleLocation = () => {
setUserLocation(e.target.value);
};
useEffect(() => {
const loadOptions = async () => {
const data = await fetch(
'https://servicodados.ibge.gov.br/api/v1/localidades/estados/'
).then((response) => {
return response.json();
});
setStateList(data);
};
loadOptions();
}, []);
return (
<form>
<div className="user-country">
<label className="white-label">Local</label>
<div className="input-icon-wrapper">
<div className="icon-input w-embed"></div>
<select
className="select-field white-select w-select"
id="locationField"
name="locationField"
onChange={handleLocation}
>
{stateList.map((state) => {
return (
<option key={state.nome} value={state.nome}>
{state.sigla}
</option>
);
})}
</select>
</div>
</div>
</form>
);
};
Hope it helps! keep up the good work and feel free to reach out in case you're still stuck!

Load More with paginated api

I'm making a Blog Web Page and the API I'm using to build it, it's already paginated so it returns 10 objects in every request I make.
But the client wants the page to have a "load more" button, in each time the user click on it, it will keep the already loaded data and load more 10 objects.
So far, I've made the button call more 10 new objects, everytime I clicked on it but I also need to keep the already loaded data.
This is my file so far:
MainPage.js
import React, { useState, useEffect} from 'react';
const MainPage = () => {
const [blogs, setBlogs] = useState('');
const [count, setCount] = useState(1);
useEffect(() => {
fetch("https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page="+count)
.then((response) => response.json())
.then((json) => {
console.log(json)
setBlogs(json)
})
}, [count])
const clickHandler = () => {
console.log(count);
return setCount( count+1)
}
return (
<div>
<p>All the recent posts</p>
{ blogs && blogs.map((blog) => {
return (
<div key={blog.id}>
<img width="100px" src={blog._embedded["wp:featuredmedia"][0].source_url}/>
<p>{blog.title["rendered"]}</p>
</div>
)
})
}
<button onClick={clickHandler}>LoadMore</button>
</div>
)
}
export default MainPage;
The idea is pretty simple. Just concatenate arrays using the Spread syntax as follows.
var first =[1, 2, 3];
var second = [2, 3, 4, 5];
var third = [...first, ...second];
So, do this thing when you're clicking the load more button.
Here I've come up with handling the whole thing:
Firstly, I will call a function inside the useEffect hook to load some blog posts initially. Secondly I've declared an extra state to show Loading and Load More text on the button.
Here is the full code snippet:
import React, { useState, useEffect } from "react";
const MainPage = () => {
const [loading, setLoading] = useState(false);
const [blogs, setBlogs] = useState([]);
const [count, setCount] = useState(1);
useEffect(() => {
const getBlogList = () => {
setLoading(true);
fetch(
"https://blog.apiki.com/wp-json/wp/v2/posts?_embed&categories=518&page=" +
count
)
.then((response) => response.json())
.then((json) => {
setBlogs([...blogs, ...json]);
setLoading(false);
});
};
getBlogList();
}, [count]);
return (
<div>
<p>All the recent posts</p>
{blogs &&
blogs.map((blog) => {
return (
<div key={blog.id}>
<img
width="100px"
src={blog._embedded["wp:featuredmedia"][0].source_url}
/>
<p>{blog.title["rendered"]}</p>
</div>
);
})}
{
<button onClick={() => setCount(count + 1)}>
{loading ? "Loading..." : "Load More"}
</button>
}
</div>
);
};
export default MainPage;
According to React documentation:
If the new state is computed using the previous state, you can pass a function to setState.
So you could append newly loaded blog posts to the existing ones in useEffect like this:
setBlogs((prevBlogs) => [...prevBlogs, ...json])
I would also set the initial state to an empty array rather than an empty string for consistency:
const [blogs, setBlogs] = useState([]);

How to pass data from child to parent and render content based on selected value in dropdown?

I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.
Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally?
Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?
Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.
PokemonList component:
import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';
const PokemonList = () => {
const [index, setIndex] = useState(0);
const [type, setType] = useState('');
function selectedType(type) { // value from filter dropdown
setType(type)
console.log("handled")
console.log(type)
}
const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
result.results.sort((a, b) => a.name < b.name ? -1 : 1);
return (
<section>
<PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
<div className="pokemon-list">
<div className="pokemons">
{result.results.map((pokemon) => (
<PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
))}
</div>
<div className="pagination">
<button
onClick={() => setIndex(index - 9)}
disabled={result.previous === null}
>
Previous
</button>
<button
onClick={() => setIndex(index + 9)}
disabled={result.next === null}
>
Next
</button>
</div>
</div>
</section>
)
}
export default PokemonList;
PokemonCard component:
import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';
const PokemonCard = ({ pokemon }) => {
const { name } = pokemon;
const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
const { types, abilities } = data;
// types[0].type.name <---- value I want to pass to PokemonList.js
return (
<div className='pokemon-card'>
<div className='pokemon-card__content'>
<img
className='pokemon-card__image'
src={data.sprites.front_default}
alt={name}
/>
<div className='pokemon-card__info'>
<p className='pokemon-card__name'>Name: {name}</p>
<p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
<p className='pokemon-card__categories'>Category: {types[0].type.name}</p>
</div>
</div>
<Link className='pokemon-card__link' to={{
pathname: `/${name}`,
state: data
}}>
View Details
</Link>
</div>
)
}
export default PokemonCard;
PokemonFilter component:
import './PokemonFilter.css';
import useSWR from 'swr';
const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {
const url = `https://pokeapi.co/api/v2/type/`;
const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);
if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>
function filteredTypeHandler(e) {
console.log(e.target.value);
onSelectedType(e.target.value);
}
console.log(selectedPokemonType)
return(
<div className="pokemon-types__sidebar">
<h2>Filter Pokémon by type</h2>
<select
name="pokemon-type"
className="pokemon-types__filter"
onChange={filteredTypeHandler}
>
<option value="All">Filter By Type</option>
{result.results.map((type) => {
return (
<option key={type.name} value={type.name}> {type.name}</option>
)
})}
</select>
</div>
)
}
export default PokemonFilter;
Here is an example to improve, modify, ... I didn't test, it's just a visual example.
I don't know about useSWR sorry, I use axios in my example...
If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.
PokemonList.js
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // or swr
import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';
export default function PokemonList() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState('');
// Executed every first render
useEffect(() => {
getData();
}, []);
// Executed only when filter changes
useEffect(() => {
getDataByTypes(filter);
}, [filter]);
// Get data
const getData = async () => {
const uri = 'https://xxx';
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
};
// Get data by types
const getDataByTypes = async (filter) => {
const uri = `https://xxx/type/${filter}...`;
if (filter) {
try {
const response = await axios.get(uri);
setData(response.data...);
} catch (error) {
console.log(error);
}
}
};
return (
<div className="main">
<PokemonFilter filter={filter} setFilter={setFilter} />
<div className="container">
<div className="cards-container">
{data.map((d) => (
<PokemonCard key={d.name} data={d} />
))}
</div>
</div>
</div>
);
}
PokemonCard.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonCard({ data }) {
const [pokemons, setPokemons] = useState();
useEffect(() => {
getPokemons(data);
}, [data]);
// Get Pokemons
const getPokemons = async (data) => {
const uri = `https://xxx/pokemon/${data.name}/`;
try {
const response = await axios.get(uri);
setPokemons(response.data...);
} catch (error) {
console.log(error);
}
};
return (
<div>
{pokemons && (
<div className="card">
<img src={pokemons.sprites.front_default} alt={pokemons.name} />
<p>{pokemons.name}</p>
<p>{pokemons.abilities[0].ability.name}</p>
<p>{pokemons.types[0].type.name}</p>
</div>
)}
</div>
);
}
PokemonFilter.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function PokemonFilter({ filter, setFilter }) {
const [types, setTypes] = useState([]);
useEffect(() => {
getType();
}, []);
// Get Type
const getType = async () => {
const uri = 'https://xxx/type/';
try {
const response = await axios.get(uri);
setTypes(response.data.results....);
} catch (error) {
console.log(error);
}
};
const handleFilter = (e) => {
setFilter(e.target.value);
};
return (
<select onChange={handleFilter} value={filter}>
<option>Filter by type</option>
{types.map((type) => {
return (
<option key={type.name} value={type.name}>
{type.name}
</option>
);
})}
</select>
);
}

Search box with React-Query

I am trying to implement Product search by text. Fetching data with react-query. The following implementation is working but it does not feel right to me. Let me know if I am overdoing it and if there is a simpler solution with react-query.
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useQueryClient } from 'react-query';
import ProductCard from '#/components/cards/ProductCard';
import { useQueryProducts } from '#/hooks/query/product';
import { selectSearch } from '#/store/search';
// function fetchProductsByFilter(text){}
const Shop = ({ count }) => {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const { text } = useSelector(selectSearch);
const productsQuery = useQueryProducts(count);
useEffect(() => {
setProducts(productsQuery.data);
setLoading(false);
}, []);
const queryClient = useQueryClient();
useEffect(() => {
const delayed = setTimeout(() => {
queryClient.prefetchQuery(['searchProductsByText'], async () => {
if (text) {
const data = await fetchProductsByFilter(text);
setProducts(data);
setLoading(false);
return data;
}
});
}, 300);
return () => clearTimeout(delayed);
}, [text]);
return (
<div className="container-fluid">
<div className="row">
<div className="col-md-3">search/filter menu</div>
<div className="col-md-9">
{loading ? (
<h4 className="text-danger">Loading...</h4>
) : (
<h4 className="text-danger">Products</h4>
)}
{products.length < 1 && <p>No products found</p>}
<div className="row pb-5">
{products.map((item) => (
<div key={item._id} className="col-md-4 mt-3">
<ProductCard product={item} />
</div>
))}
</div>
</div>
</div>
</div>
);
};
// async function getServerSideProps(context) {}
export default Shop;
It doesn't seem very idiomatic to me. With react-query, the key to using filters are to put them into the query key. Since react-query refetches every time the key changes, you'll get a refetch every time you change a filter, which is usually what you want. It's a very declarative way of doing things. No useEffect needed at all.
If this happens when choosing something from a select or clicking an apply button, that's really all you need:
const [filter, setFilter] = React.useState(undefined)
const { data, isLoading } = useQuery(
['products', filter],
() => fetchProducts(filter)
{ enabled: Boolean(filter) }
)
Here, I am additionally disabling the query as long as the filter is undefined - fetching will start as soon as we call setFilter.
if typing into a text field is involved, I'd recommend some debouncing to avoid firing off too many requests. The useDebounce hook is very good for that. You'd still have the useState, but you'd use the debounced value for the query:
const [filter, setFilter] = React.useState(undefined)
const debouncedFilter = useDebounce(filter, 500);
const { data, isLoading } = useQuery(
['products', debouncedFilter],
() => fetchProducts(debouncedFilter)
{ enabled: Boolean(debouncedFilter) }
)
If this happens when choosing something from a select or clicking an apply button, that's really all you need:
const [filter, setFilter] = useState<string>('')
const isEnabled = Boolean(filter)
const { data, isLoading } = useQuery(
['products', filter],
() => fetchProducts(filter)
{ enabled: enabled: filter ? isEnabled : !isEnabled, }
)
<input type="text" onChange={(e) => setFilter(e.target.value)}/>

Categories

Resources