React - API data only showing if I manually refresh the page - reactjs

I'm new to React and I created a small admin panel where you can add, edit, remove products. I would like to display 3 products from API when someone opens the app the first time and don't have edited products yet, but this data only shows if I manually refresh the page. I only want to display that if edited product is false, but initially I set edited products to false yet somehow it's not displaying, though I see the data as well as edited is set to false in the console.
Demo
https://react-storeadminpanel.herokuapp.com/
Here is the related code:
const Products = () => {
const {products, setProducts, setAllProducts, allProducts, editedItems, setEditedItems} = useProduct();
useEffect(() => {
async function fetchProducts() {
const res = await axios.get('https://a.nacapi.com/LimeGreen/products/').catch(err => console.log(err));
if(res) {
setProducts(res.data)
setEditedItems(false);
if(allProducts.length === 0 && editedItems === false) setAllProducts(products);
if(allProducts.length === 0 && editedItems === true) setAllProducts(allProducts);
if(allProducts.length > 0) setAllProducts([...allProducts]);
}
return res;
}
fetchProducts();
}, []);
return (
<Wrapper classname="wrapper">
<h1>All Products</h1>
<Cards>
{!!allProducts.length && (
allProducts.map(product => (
<ProductCard name={product.name} description={product.Description} price={product.Price} discount={product.Discount} key={product.uuid}/>
))
)}
</Cards>
</Wrapper>
)
}
The context, where I use LocalStorage
export const ProductContext = React.createContext();
export function useProduct() {
return useContext(ProductContext);
}
export function ProductProvider({children}) {
const [products, setProducts] = useLocalStorage('Api Data', []);
const [addedProduct, setAddedProduct] = useLocalStorage('Added Item', []);
const [allProducts, setAllProducts] = useLocalStorage('All Products', []);
const [editedItems, setEditedItems ] = useLocalStorage('Edited', false);
const [isAdded, setIsAdded] = useState(false);
const value = {
products,
setProducts,
addedProduct,
setAddedProduct,
allProducts,
setAllProducts,
editedItems,
setEditedItems,
isAdded,
setIsAdded,
}
return (
<ProductContext.Provider value={value}>
{children}
</ProductContext.Provider>
)
}
And Code where I set edit products to true
const ProductEdit = () => {
const {allProducts, setAllProducts, setEditedItems} = useProduct();
const [editProductId, setEditProductId] = useState(null);
const [editForm, setEditForm] = useState({
name: "",
Description: "",
Price: "",
Discount: "",
})
const saveEditHandler = (e) => {
e.preventDefault();
const fieldName = e.target.getAttribute("name");
const fieldValue = e.target.value;
const newForm = {...editForm};
newForm[fieldName] = fieldValue;
setEditForm(newForm);
}
const editHandler = (e, product) => {
e.preventDefault();
setEditProductId(product.uuid);
const formValues = {
name: product.Name,
Description: product.Description,
Price: product.Price,
Discount: product.Discount
}
setEditForm(formValues);
}
const submitEditsHandler = (e) => {
e.preventDefault();
const editedProduct = {
name: editForm.Name,
Description: editForm.Description,
Price: editForm.Price,
Discount: editForm.Discount,
uuid: editProductId
}
const newProducts = [...allProducts];
const index = allProducts.findIndex((product) => product.uuid === editProductId);
newProducts[index] = editedProduct;
setAllProducts(newProducts);
setEditedItems(true);
setEditProductId(null);
}
const cancelHandler = () => {
setEditProductId(null);
}
const deleteHandler = (productId) => {
const newProducts = [...allProducts];
const index = allProducts.findIndex((product) => product.uuid === productId);
newProducts.splice(index, 1);
setAllProducts(newProducts);
setEditedItems(true);
};

Related

Is there a way to make the data fetch only once to avoid API calls limit?

I have these codes trying to implement a crypto return calculator that takes in a coin ID, a buy and sell date and the coin amount.
My issue is that the API is being fetched after every input and by the time I reach the sell date, my API limit has been reached.
Is there a way to prevent this from happening?
Here are the codes below.
export default function App() {
const [trade, setTrade] = useState({
sellData: {},
buyData: {},
gains: 0
});
const coinList = [
{ id: 0, name: "bitcoin" },
{ id: 1, name: "ethereum" },
{ id: 2, name: "tezos" },
{ id: 3, name: "cardano" }
];
const [buyDate, setBuyDate] = useState("");
const [sellDate, setSellDate] = useState("");
const [volume, setVolume] = useState(0);
const [coin, setCoin] = useState("");
const coingeckoUrl = (coin, date) => {
return `https://api.coingecko.com/api/v3/coins/${coin}/history?date=${date}&localization=false`;
};
const calcGains = () => {
setTrade({
...trade,
gains:
(trade.sellData.market_data?.current_price.usd -
trade.buyData.market_data?.current_price.usd) *
volume
});
};
const coingeckoFetch = async (buy, coin, date) => {
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
coingeckoFetch(true, coin, val);
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
coingeckoFetch(false, coin, val);
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
coingeckoFetch(null, coin, val);
};
return (
<div className="App">
<select defaultValue={coin} onChange={(val) => handleCoinChange(val)}>
{coinList.map((item) => (
<option key={item.id}>{item.name}</option>
))}
</select>
<input
placeholder="Insert Buy Date"
defaultValue={buyDate}
onChange={(val) => handleBuyChange(val)}
/>
<h3> {trade.buyData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Sell Date"
defaultValue={sellDate}
onChange={(val) => handleSellChange(val)}
/>
<h3> {trade.sellData.market_data?.current_price.usd} USD</h3>
<input
placeholder="Insert Amount of Tokens"
value={volume}
onChange={(e) => setVolume(e.target.value)}
/>
<h3>{volume}</h3>
<button onClick={calcGains}> Calculate </button>
<h3>{trade.gains} USD</h3>
</div>
);
}
Thank you for your help.
Summary
if you are just don't want to request too many times.
Maybe you can add a state like
const [fetchNow, setFetchNow] = useState(false);
And in your handleChange() functions, do not carry out the coingeckoFetch(). But execute the setFetchNow()
like this
const handleBuyChange = (e) => {
let val = e.target.value;
setBuyDate(val);
// or your buy condition
if (val) {
setFetchNow(true)
}
};
const handleSellChange = (e) => {
let val = e.target.value;
setSellDate(val);
// or your sell condition
if (val) {
setFetchNow(true)
}
};
const handleCoinChange = (e) => {
let val = e.target.value;
setCoin(val);
// or your coin condition
if (val) {
setFetchNow(true)
}
};
Then add a useEffect(), and use a conditional if statement to determine that whether the coingeckoFetch() be carry out or not
useEffect(()=>{
if (fetchNow) {
coingeckoFetch(true, coin, val);
}
}, [fetchNow]);
Other
Or just simply add conditional if statement in your coingeckoFetch()
like
const coingeckoFetch = async (buy, coin, date) => {
// conditional if statement
if (buy !== "MyCondition") {
return;
}
else if (coin !== "MyCondition") {
return;
}
else if (date !== "MyCondition") {
return;
}
fetch(coingeckoUrl(coin, date)).then((response) =>
response.json().then((jsonData) => {
if (buy) {
setTrade({ ...trade, buyData: jsonData });
} else {
setTrade({ ...trade, sellData: jsonData });
}
})
);
};

React AsyncSelect get options from server when inputValue.length > N and user stops typing

I am developing a select with input of type text that get the options from API server and display it on the select options. I am using AsyncSelect to achive it but now, I want only to call the API when the inputValue.length >= 6 and when the user stops typing.
This is the code:
const AppCustomSelect: FC<Props> = ({
value = {value:"", label:""},
onChange,
}) => {
const [selectedValue, setSelectedValue] = useState<MultiValue<ValueLabel>>([value]);
const [inputValue, setInputValue] = useState<string>("");
const [selectableOptions, setSelectableOptions] = useState<ValueLabel[]>([]);
const promiseOptions = async (inputValue: string) => {
let valueLabelSuppliers: ValueLabel[] = [];
if(inputValue.length >= 6) {
const suppliersList = await getSuppliers({country:"", text: inputValue, status:""});
suppliersList.map((supplier) => {
valueLabelSuppliers.push(mapSupplierToValueLabel(supplier))
});
}
setSelectableOptions(valueLabelSuppliers);
return (valueLabelSuppliers);
}
const mapSupplierToValueLabel = (supplier: SupplierSelectableListModel): ValueLabel => {
return {
value: supplier.sapCode,
label: supplier.name
}
}
const onInputChange = (option: string, {action}: InputActionMeta ) => {
if (action === "input-change") {
const optionLength = option.length;
const inputValueLength = inputValue.length;
const myObject = {
label: option,
value: option
};
const newInputValue =
(optionLength < inputValueLength)
? option
: inputValue + option[option.length - 1];
setInputValue(newInputValue);
}
};
const onChange2 = (option: MultiValue<ValueLabel>) => {
setSelectedValue(option);
setInputValue("");
onChange(option);
}
return (
<AsyncSelect
isMulti
inputValue={inputValue}
onInputChange={onInputChange}
cacheOptions
loadOptions={promiseOptions}
onChange={onChange2}
/>
);
};

How to organize useState and useQuery - Apollo/Graphql

I'm trying to pass data from useState([]) to the variables for filtering products. It works, but on very strange way.
When I click on a category checkbox, it is not checked, but the products are filtered, when I click on another category, again checkbox didn't checked, but already two values in the array useState([1,2]), and when I click again, the checkbox is checked, but is removed from the array useState([]).
const GET_PRODUCTS = gql`
query GetProducts($filterByCategory: [ID]) {
products(filters: {category: {id:{in: $filterByCategory}}}) {
....
}
}
`
const VacanciesPage = () => {
const [selectCategories, setSelectedCategories] = useState([]);
const getSetSelectedCategories = (category) => {
console.log(category);
if(selectCategories.includes(category)){
setSelectedCategories(selectCategories.filter(item => item != category));
return;
}
setSelectedCategories([...selectCategories, category]);
}
useEffect(() => {
console.log(selectCategories);
},[selectCategories]);
const { loading, error, data } = useQuery(GET_PRODUCTS, {
variables: { "filterByCategory": selectCategories},
});
if (loading) return null;
if (error) return `Error! ${error}`;
const { products, categories } = data;
return(
{categories.data.map(category => (
<label key={category.id} className="inline-flex items-center mt-3 mr-3">
<input type="checkbox" className="w-5 h-5" value={category.id} onChange={e => getSetSelectedCategories(+e.target.value)}/><span className="ml-2 text-gray-700">{category.attributes.name}</span>
</label>
))}
);
}
I completely rewrote the code, moved the variables to a separate function and everything finally worked.
const [selectedCategories, setSelectedCategories] = useState([]);
const [searchStatus, setSearchStatus] = useState(false);
useEffect(() => {
console.log(selectedCategories);
},[selectedCategories]);
const { loading, error, data } = useQuery(GET_PRODUCTS);
const { loading: loadingCategories, error: errorCategories, data: categories } = useQuery(GET_CATEGORIES);
const [ getFilteredProducts,
{ loading: filterLoading, error: filterError, data: filteredProducts }
] = useLazyQuery(FILTER_PRODUCTS_QUERY);
if (loading || loadingCategories || filterLoading) return "Loading";
if (error || errorCategories || filterError) return `Error! ${error}`;
const { products: allProducts } = data;
const { categories: allCategories } = categories;
const clearSearch = () => {
setSelectedCategories([]);
setSearchStatus(false);
};
const searchProducts = () => {
let filter = {};
setSearchStatus(true);
if (Object.keys(filter).length === 0) {
if (!selectedCategories) {
setSearchStatus(false);
return;
}
}
getFilteredProducts({
variables: {
filterByCategories: selectedCategories
? selectedCategories
: allCategories.data.map((category) => category.id),
},
});
};
const dataset = searchStatus && filteredProducts ? filteredProducts?.products : allProducts;

fetch data is updated but array and state is not updated

i am woking on weather api and storing perticular data in an array arr but value is not available in arr. also state arrdata is null too.
i tried to not use state but still not getting data in arr . it show reading undefined value.
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[4].main !== null){
const arr = [];
for (let i = 0; i <= 40; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i].main.temp,
Min_temp: getData.list[i].main.temp_min,
Max_temp: getData.list[i].main.temp_max,
date: getData.list[i].dt_txt,
mood: getData.list[i].weather[0].main,
weathermoodIcon: getData.list[i].weather[0].icon,
Humidity: getData.list[i].main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo()
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<Input onChangeValue={onInputChange} onSubmit={onSubmitCity} />
</>
);
}
This seems to be working. Please do not forget to use optional chaining
import {useState, useEffect } from 'react';
export default function App() {
const [cityName, setCityName] = useState("delhi");
const [arrData, setArrData] = useState(null);
const getWeatherInfo = async () => {
const url = "https://api.openweathermap.org/data/2.5/forecast";
const api = "4beffc863037e89f0f181d893d1cf79b";
fetch(`${url}?q=${cityName}&units=metric&appid=${api}`)
.then((res) => res.json())
.then((getData) => {
if(getData.list[40]?.main !== null){
const arr = [];
console.log(getData.list)
for (let i = 0; i <= 4; i++) {
if (i % 8 === 0) {
arr.push({
temprature: getData.list[i]?.main.temp,
Min_temp: getData.list[i]?.main.temp_min,
Max_temp: getData.list[i]?.main.temp_max,
date: getData.list[i]?.dt_txt,
mood: getData.list[i]?.weather[0].main,
weathermoodIcon: getData.list[i]?.weather[0].icon,
Humidity: getData.list[i]?.main.humidity,
});
}}
setArrData(arr);
}});
};
useEffect(() => {
getWeatherInfo();
}, []);
console.log(arrData)
const onInputChange = (e) => {
setCityName(e.target.value);
};
const onSubmitCity = () => {
getWeatherInfo();
};
return (
<>
<input onChange={onInputChange} onSubmit={onSubmitCity} />
<h1> {JSON.stringify(arrData)} </h1>
<button onClick = {onSubmitCity}> Submit </button>
</>
);
}

useState not updating the state

I'm trying to filter users with input search inside the function searchByName.
I manage to get the right result in copyUsersvariable but unfortunately it does not reflect the change inside the state.
Forgot to mention, using bare React-App with hooks and typescript.
For example, i write 'p' in the input and recieve the right filtered array in copyUsers variable but then i push it into the state it does not update.
Attaching screenshot for understanding the situation better:
what i have tried instead setFilteredUsers(copyUsers):
setFilteredUsers(() => [...filteredUsers, copyUsers]);
setFilteredUsers(() => copyUsers);
main component:
const { value } = useSelector(({ test }: any) => test);
const [users, setUsers] = useState<Users>([]);
const [filteredUsers, setFilteredUsers] = useState<Users>([]);
const [searchNameValue, setSearchNameValue] = useState<string>("");
const [selectedUser, setSelectedUser] = useState<User>();
const [searchOrderBy, setSearchOrderBy] = useState<string>("");
const dispatch = useDispatch();
useEffect(() => {
const get = async () => {
const response = await ApiTest.testGet();
setUsers(response);
setSearchOrderBy("desc");
};
get();
}, []);
useEffect(() => {
searchByName();
setNewOrder();
}, [searchOrderBy]);
useEffect(() => {
console.log('search value changed!', searchNameValue);
searchByName();
setNewOrder()
}, [searchNameValue]);
const setNewOrder = () => {
if (users.length) {
let copyUsers = JSON.parse(
JSON.stringify(filteredUsers.length ? filteredUsers : users)
);
switch (searchOrderBy) {
case "desc":
copyUsers.sort((a: any, b: any) => {
if (a.id > b.id) {
return -1;
}
if (b.id > a.id) {
return 1;
}
return 0;
});
break;
case "asc":
copyUsers.sort((a: any, b: any) => {
if (b.id > a.id) {
return -1;
}
if (a.id > b.id) {
return 1;
}
return 0;
});
break;
default:
break;
}
filteredUsers.length ? setFilteredUsers(copyUsers) : setUsers(copyUsers);
}
};
const searchByName = () => {
if (searchNameValue) {
let copyUsers = JSON.parse(JSON.stringify(users));
copyUsers = copyUsers.filter((user: User) => {
return user.name
.toLocaleLowerCase()
.includes(searchNameValue.toLocaleLowerCase());
});
console.log("copyUsers =", copyUsers);
setFilteredUsers(copyUsers);
console.log("filteredUsers =", filteredUsers);
}
};
const UserCards =
!!users.length &&
(searchNameValue ? filteredUsers : users).map(user => {
return (
<UserCard
selectedUser={selectedUser}
setSelectedUser={(user: User) => setSelectedUser(user)}
user={user}
/>
);
});
return (
<div>
<FilterBar
searchOrderBy={searchOrderBy}
searchSetOrderBy={(value: string) => setSearchOrderBy(value)}
setSearchNameValue={(value: string) => setSearchNameValue(value)}
searchNameValue={searchNameValue}
/>
<div style={{ display: "flex", flexFlow: "wrap" }}>{UserCards}</div>
</div>
);

Resources