useState not updating the state - reactjs

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>
);

Related

How to test react component with custom hook

I'm trying to renders items, but due to a custom hook (useBicycles), the map method passes through an empty array. How i can to test this?
My component:
function Items(props: Props) {
const { bicycles } = props;
const filteredBicycles = useBicycles(bicycles);
return (
<div className="items" data-testid="items">
{filteredBicycles.map((item) => {
return <Item item={item} key={uuidv4()} />;
})}
</div>
);
}
My test:
test('renders itself with data without filters', async () => {
renderWithRedux(<Items bicycles={fakeData} isLoading={false} />, FakeInitialState);
const itemsElem = screen.getByTestId('items');
expect(itemsElem).toBeInTheDocument();
const items = await screen.findAllByTestId('item'); <<< ERROR NOT FOUNDED ITEM
expect(items.length).toEqual(QUANTITY_PRODUCTS);
});
Custom hook useBicycles:
It should return filtered array based on the values from store but result in the test I get empty array. If I remove the hook call in the component then everything renders normally.
export const useBicycles = (bicycles: IBicycle[]) => {
const { searchValue } = useAppSelector((state) => state.searchReducer);
const { sortOption } = useAppSelector((state) => state.sortReducer);
let filteredBicycles = bicycles;
// search
filteredBicycles = useMemo(() => {
return bicycles.filter((item) => {
return item.name.toLowerCase().includes(searchValue.toLowerCase());
});
}, [searchValue, bicycles]);
// sorting
filteredBicycles = useMemo(() => {
switch (sortOption) {
case SortOptions.name_asc:
return [...filteredBicycles].sort((a, b) => a.name.localeCompare(b.name));
case SortOptions.name_desc:
return [...filteredBicycles].sort((a, b) => b.name.localeCompare(a.name));
case SortOptions.price_asc:
return [...filteredBicycles].sort((a, b) => a.price - b.price);
case SortOptions.price_desc:
return [...filteredBicycles].sort((a, b) => b.price - a.price);
case SortOptions.quantity_asc:
return [...filteredBicycles].sort((a, b) => a.quantity - b.quantity);
case SortOptions.quantity_desc:
return [...filteredBicycles].sort((a, b) => b.quantity - a.quantity);
default:
return filteredBicycles;
}
}, [sortOption, filteredBicycles]);
return filteredBicycles;
};

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>
</>
);
}

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

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);
};

I have a question about react array clearing

There is an array in the parent class(TodolistActivity), and the child class(TaskCallFunc) displays the elements in the array. When I use a.list= []; to clear the array, there is no clearing on the page
but a.list.length = 0 is ok. why?
Here is my code:
interface IListData {
list: IActivityData[]
}
interface IActivityData {
id: number,
content: string,
finish: boolean
}
export function TodolistActivity(activty: IListData) {
const [acty, setActivity] = useState(activty);
const [content, setContent] = useState('');
const input_ref = React.createRef<HTMLInputElement>();
const [selectCount, setSelect] = useState(0);
const handleAdd = () => {
if (input_ref.current) {
if (input_ref.current.value === '') {
alert("输入内容 不能为空!");
return;
}
let id = acty.list.length;
acty.list.unshift({ id: id, content: content, finish: false })
let a = { ...acty }
setActivity(a);
input_ref.current.value = "";
}
}
const calcuateSelect = () => {
let a = acty.list.filter((v, i) => { return v.finish === true })
setSelect(a.length);
}
const handleChange = (input: React.ChangeEvent<HTMLInputElement>) => {
setContent(input.target.value);
}
const clearTask = () => {
let a = { ...acty};
a.list= [];
//a.list.length = 0;
setActivity(a);
}
return (
<div>
<input type='text' onChange={handleChange} ref={input_ref} />
<button className="add task" onClick={handleAdd}>add task</button>
<button className="clear task" onClick={clearTask}>clear task</button>
{console.log(acty)}
<TaskCallFunc data={acty} action={() => { calcuateSelect() }} />
<br />
<label htmlFor="">select{selectCount}/{acty.list.length}</label>
</div>
);
}
interface ItaskCell {
data: IListData,
action: () => void
}
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
const HandleSlecet = (x: number) => {
for (let index = 0; index < data.data.list.length; index++) {
if (data.data.list[index].id === x) {
let newState = { ...data };
newState.data.list[index].finish = !data.data.list[index].finish;
setData(newState);
data.action();
}
}
}
const handleMap = () => {
return data.data.list.map((v, i) => { return <li key={v.id}>{v.id}: {v.content} <input type="checkbox" checked={v.finish} onChange={() => { HandleSlecet(v.id) }} /> </li> });
}
return (
<ul>{handleMap()}</ul>
);
}
If you know the answer, please let me know thank you
TaskCallFunc component doesn't "listen" for changes on the taskData prop to update the local copy stored in state. Use an useEffect hook with a dependency on taskData prop to update the state when it changes.
function TaskCallFunc(taskData: ItaskCell) {
const [data, setData] = useState(taskData);
useEffect(() => {
setData(taskData);
}, [taskData]);
...
You can clear the array easely by doing setActivity({ list: [] }) consider also to add useEffect as Drew says to listen for changes

Multiple filters with React Hook - Chaining function

Hello I have created a search bar with a multipl filter, it works but the functions are too dependent on each other. The problem here is that the functions are handling multiple cases.
would it be possible to lighten each function by chaining them and how ? I don't really get chaining method.
thanks
import React, { useState, useEffect } from "react";
import Search from "./Search";
import Anime from "./Anime";
import "./App.css";
const KIJAN_API_URL = "https://api.jikan.moe/v3/top/anime/1/upcoming";
const App = () => {
const [animes, setAnimes] = useState([]);
const [sortedAnimes, setSortedAnimes] = useState([]);
const [searchValue, setSearchValue] = useState("")
const [filterByType, setFilterByType] = useState("");
const [filterByYear, setFilterByYear] = useState("");
useEffect(() => {
fetch(KIJAN_API_URL)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong");
}
})
.then(jsonResponse => {
setAnimes(jsonResponse.top);
})
.catch(error => {
console.log(error);
});
}, []);
useEffect(() => {
const callFilterByType = result => {
if (filterByType === "") {
callFilterByYear(result);
console.log(result);
} else {
result = result.filter(anime => anime.type === filterByType);
callFilterByYear(result);
console.log(result);
}
};
const callFilterByYear = result => {
if (filterByYear === "") {
setSortedAnimes(result);
} else {
const regex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => regex.test(anime.start_date));
setSortedAnimes(result);
console.log(result);
}
};
if (searchValue === "") {
callFilterByType(animes);
} else {
const regex = new RegExp(`${searchValue}`, "gi");
console.log("search : ", searchValue);
const result = animes.filter(anime => regex.test(anime.title));
callFilterByType(result);
console.log(result);
}
}, [searchValue, animes, filterByType, filterByYear]);
return (
<div className="App">
<Search
searchValue={searchValue}
setSearchValue={setSearchValue}
filterByType={filterByType}
setFilterByType={setFilterByType}
filterByYear={filterByYear}
setFilterByYear={setFilterByYear}
/>
{sortedAnimes.length > 0 ? (
sortedAnimes.map((anime, index) => {
return <Anime key={index} anime={anime} />;
})
) : (
<span>Aucune correspondance</span>
)}
</div>
);
};
export default App;
SandBox Sample
You can do first round of simplification like this:
useEffect(() => {
let result = [...animes];
if(searchValue) {
const searchRegex = new RegExp(`${searchValue}`, "gi");
result = result.filter(anime => searchRegex.test(anime.title));
}
if(filterByType) {
result = result.filter(anime => anime.type === filterByType);
}
if(filterByYear) {
const yearRegex = new RegExp(`${filterByYear}`, "gi");
result = result.filter(anime => yearRegex.test(anime.start_date));
}
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
It can be reduced to more compact form, like:
useEffect(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
const result = animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
setSortedAnimes(result);
}, [searchValue, animes, filterByType, filterByYear]);
More idiomatic way would be use use momoisation hook. i.e. Remove sortedAnimes as state and
const sortedAnimes = useMemo(() => {
const searchRegex = searchValue && new RegExp(`${searchValue}`, "gi");
const yearRegex = filterByYear && new RegExp(`${filterByYear}`, "gi");
return animes.filter(anime =>
(!searchRegex || searchRegex.test(anime.title)) &&
(!filterByType || anime.type === filterByType)) &&
(!yearRegex || yearRegex.test(anime.start_date))
)
}, [searchValue, animes, filterByType, filterByYear]);
try this
if you are using filter method inside jsx then you try this method.
Let me brief it,
consider userInfo like an object containing fields like name, email, location etc. so, if you want your filter method to provide your search results based on these fields value then you can use something like this in jsx.
{userInfo.filter((user) => (
user.name.toLowerCase().includes(cloneSearchTerm)
||
user.email.toLowerCase().includes(cloneSearchTerm)
||
user.location.toLowerCase().includes(cloneSearchTerm)
)
).map((user, idx) => (
<div key={idx}>
<span>{user.name}</span>
<span>{user.email}</span>
<span>{user.location}</span>
</div>
))}

Resources