My question is related with react hooks. I'm doing api project for cars brand and model. As the first step when i selected from select element, thats will be install second select whats related with my first selected element. But i have a problem. when i select first step thats no appear console but when i second select console is appear my first select
function Home() {
const [api, setApi] = useState({});
const [userId, setUserId] = useState({});
const [errors, setErrors] = useState(true);
const [error, setError] = useState(false);
const id = useDispatch((userId)=>modelAction(userId));
let key;
let history = useHistory();
function handleClick(e){
key = api.filter(item => item.var_title === e.target.value);
setUserId({...key});
console.log(userId);
id({
type: 'MODELS',
id: userId
})
history.push('/model')
}
useEffect(()=>{
axios.get('https://api.carecusoft.com/tr/v1/chiptuning?key=testSA65D46ASD4AS8F4AS6F4A68')
.then(res=> setApi(res.data))
.catch(err => setErrors(true));
console.log(api);
},[userId]);
return (
<div>
<select onChange={e=>handleClick(e)} as={Link} to='/model'>
<option>Marka seƧ</option>
{
Object.values(api).map(item => {
const {id, var_title} = item;
return(
<option key={id} value={var_title} >{var_title}</option>
)
})
}
</select>
</div>
);
}
export default Home;
Your console log doesn't need to be in the useEffect.
Since it's not a side effect.
Have the console log outside above your return
Because you log your data inside handleClick function where you userid set. You need to log your data in your useEffect to see changes.
If you set your variables inside your Render with hooks in a
functional component changes in your variable appears with it's new value
in useEffect because you give the variable as a parameter to useEffect.
Like this example;
useEffect(()=>{
console.log('userId', userId);
},[userId]);
means, useEffect only triggered when userid changes.
Try this and you can see clearly what i mean,
useEffect(()=>{
axios.get('https://api.carecusoft.com/tr/v1/chiptuning?key=testSA65D46ASD4AS8F4AS6F4A68').then(res=> setApi(res.data))
.catch(err => setErrors(true));
console.log('api', api);
console.log('userId', userId);
id({
type: 'MODELS',
id: userId
})
},[userId]);
Hope it helps.
Related
For learning purposes, I'm creating an e-shop, but I got stuck with localStorage, useEffect, and React context. Basically, I have a product catalog with a button for every item there that should add a product to the cart.
It also creates an object in localStorage with that item's id and amount, which you select when adding the product to the cart.
My context file:
import * as React from 'react';
const CartContext = React.createContext();
export const CartProvider = ({ children }) => {
const [cartProducts, setCartProducts] = React.useState([]);
const handleAddtoCart = React.useCallback((product) => {
setCartProducts([...cartProducts, product]);
localStorage.setItem('cartProductsObj', JSON.stringify([...cartProducts, product]));
}, [cartProducts]);
const cartContextValue = React.useMemo(() => ({
cartProducts,
addToCart: handleAddtoCart, // addToCart is added to the button which adds the product to the cart
}), [cartProducts, handleAddtoCart]);
return (
<CartContext.Provider value={cartContextValue}>{children}</CartContext.Provider>
);
};
export default CartContext;
When multiple products are added, then they're correctly displayed in localStorage. I tried to log the cartProducts in the console after adding multiple, but then only the most recent one is logged, even though there are multiple in localStorage.
My component where I'm facing the issue:
const CartProduct = () => {
const { cartProducts: cartProductsData } = React.useContext(CartContext);
const [cartProducts, setCartProducts] = React.useState([]);
React.useEffect(() => {
(async () => {
const productsObj = localStorage.getItem('cartProductsObj');
const retrievedProducts = JSON.parse(productsObj);
if (productsObj) {
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts([...cartProducts, fetchedProduct]);
});
}
}
)();
}, []);
console.log('cartProducts', cartProducts);
return (
<>
<pre>
{JSON.stringify(cartProductsData, null, 4)}
</pre>
</>
);
};
export default CartProduct;
My service file with fetchProductById function:
const domain = 'http://localhost:8000';
const databaseCollection = 'api/products';
const relationsParams = 'joinBy=categoryId&joinBy=typeId';
const fetchProductById = async (id) => {
const response = await fetch(`${domain}/${databaseCollection}/${id}?${relationsParams}`);
const product = await response.json();
return product;
};
const ProductService = {
fetchProductById,
};
export default ProductService;
As of now I just want to see all the products that I added to the cart in the console, but I can only see the most recent one. Can anyone see my mistake? Or maybe there's something that I missed?
This looks bad:
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts([...cartProducts, fetchedProduct]);
});
You run a loop, but cartProducts has the same value in every iteration
Either do this:
Object.values(retrievedProducts).forEach(async (x) => {
const fetchedProduct = await ProductService.fetchProductById(x.id);
setCartProducts(cartProducts => [...cartProducts, fetchedProduct]);
});
Or this:
const values = Promise.all(Object.values(retrievedProducts).map(x => ProductService.fetchProductById(x.id)));
setCartProducts(values)
The last is better because it makes less state updates
Print the cartProducts inside useEffect to see if you see all the data
useEffect(() => {
console.log('cartProducts', cartProducts);
}, [cartProducts]);
if this line its returning corrects values
const productsObj = localStorage.getItem('cartProductsObj');
then the wrong will be in the if conditional: replace with
(async () => {
const productsObj = localStorage.getItem('cartProductsObj');
const retrievedProducts = JSON.parse(productsObj);
if (productsObj) {
Object.values(retrievedProducts).forEach(async (x) => {
const fetched = await ProductService.fetchProductById(x.id);
setCartProducts(cartProducts => [...fetched, fetchedProduct]);
});
}
}
Issue
When you call a state setter multiple times in a loop for example like in your case, React uses what's called Automatic Batching, and hence only the last call of a given state setter called multiple times apply.
Solution
In your useEffect in CartProduct component, call setCartProducts giving it a function updater, like so:
setCartProducts(prevCartProducts => [...prevCartProducts, fetchedProduct]);
The function updater gets always the recent state even though React has not re-rendered. React documentation says:
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
I need to change the useState without rendering the page.
First is it possible?
const UsersComponent = ({valueProp}) => {
const [users, setUsers] = useState(valueProp);
const [oldUsers, setoldUsers] = useState(value);
const allUsers = useSelector((state) =>
state.users
);
useEffect(() => {
dispatch(getUsersData());
}, [dispatch]);
useEffect(() => {
// assign users to state oldUsers
}, [dispatch]);
const onClickMergeTwoArrayOfUsers = () => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
console.log("filteredUsers", filteredUsers); // not changed
};
I tried everything nothing helps me.
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, [users]); // RETURN INFINITIVE LOOP
I am also try ->
useEffect(() => {
let oldUsers = collectData(oldUsers);
const filteredUsers = intersectionBy(oldUsers, valueProp, "id");
setUsers(filteredUsers); // most important
}, []);
Load only one and that doesn't mean anything to me..
I am try with useRef ,but that doesn't help me in this case.
I will try to explain the basis of the problem.
I need to get one get data. After that get on the click of a button, I need to merge oldUsers and users without rendering, change the state. That is problem.
If there is no solution to this problem, tell me what I could do to solve the problem?
I am googling but without succes ... I am also try this solution from interent ->
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
no work.
I am also try with ->
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
Here is problem because I need to asynchronous get only after that can I looping.
Using a ref is probably a better option for whatever it is you're ultimately trying to do.
Yes, it is possible, but it violates one of the core rules of React state: Do Not Modify State Directly.
React compares state values using Object.is equality, so if you simply mutate an object in state instead of replacing it with a new value that is not object-equal, then the state "update" will not cause a re-render (but this is considered a bug in your program!). Anyway, this is how you'd do it:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.17.1/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useCallback, useState} = React;
function Example () {
const [state, setState] = useState([1]);
const logState = useCallback(() => console.log(state.join(', ')), [state]);
// Don't actually do this!!!
const mutateState = () => {
setState(arr => {
arr.push(arr.at(-1) + 1);
return arr;
});
};
return (
<>
<div>{state.join(', ')}</div>
<button onClick={mutateState}>Mutate state</button>
<button onClick={logState}>Log state</button>
</>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
I got a button that triggers data fetching from fetchDataHandler, that works with useEffect, it fetch data on page load also. But then when I add addMovieHandler as dependency to useEffect to auto-fetch data when I add another movie, it just doesn't work. So I figured out I may be using it wrong, but cant figure it out.
const App = () => {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const fetchDataHandler = useCallback(async () => {
setIsLoading(true);
const respone = await fetch(
"https://react-http-3af47-default-rtdb.firebaseio.com/movies.json"
);
if (!respone.ok) {
setIsLoading(false);
throw new Error("Something went wrong!");
}
const data = await respone.json();
const movieList = [];
for (const key in data) {
movieList.push({
id: key,
title: data[key].title,
text: data[key].text,
});
}
setMovies(movieList);
setIsLoading(false);
}, []);
const addMovieHandler = useCallback(async (movie) => {
const respone = await fetch(
"https://react-http-3af47-default-rtdb.firebaseio.com/movies.json",
{
method: "POST",
body: JSON.stringify(movie),
headers: {
"Content-Type": "application/json",
},
}
);
const data = await respone.json();
console.log(data);
}, []);
useEffect(() => {
fetchDataHandler();
}, [fetchDataHandler, addMovieHandler]);
let content = <p>no movies found</p>;
if (movies.length > 0) {
content = <MovieList movies={movies} />;
}
if (isLoading) {
content = <p>loading...</p>;
}
return (
<React.Fragment>
<UserInput onAddMovie={addMovieHandler}></UserInput>
<Card>
<button onClick={fetchDataHandler}>Fetch movies</button>
</Card>
{content}
</React.Fragment>
);
};
export default App;
Try this instead:
Remove addMovieHandler from useEffect. When you hit addMovieHandler it is fetching the data anyway. So what you could to is set the movies with the latest response in the handler itself like so:
setMovies(...data)
This will replace the movie list with the newly fetched movie list. If you want to add on the old list you could do this:
setMovies([...movies, data])
This is concatenate the new fetched list to the already existing movie list and update the variable and re-render the movie list section as well.
Your useEffect is not triggering on addMovieHandler reference change because you're using useCallback with no dependencies ([] second parameter) to memoize it. This means the addMovieHandler reference won't change at all between rerenders but you're basing your useEffect calls on it doing so. In simple words - useEffect won't rerun if the provided dependencies have the same values (which is the case in your solution).
Same goes for fetchDataHandler.
In any case, this is not a good solution to the problem. You should be better-off updating your movies array on successful movie addition in the addMovieHandler itself. Same goes for other operations like delete and update.
I am trying to implement a simple search algorithm for my products CRUD.
The way I thought to do it was entering the input in a search bar, and the products that matched the search would appear instantly every time the user changes the input, without needing to hit a search button.
However, the way I tried to do it was like this:
function filterProducts (productName, productList) {
const queryProducts = productList.filter((prod)=> {
return prod.title === productName;
});
return queryProducts;
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [popupTrigger, setPopupTrigger] = useState('');
const [productDeleteId, setProductDeleteId] = useState('');
const [queryString, setQueryString] = useState('');
let history = useHistory();
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
return;
}
const queryProducts = filterProducts(queryString, productList);
setProductList(queryProducts);
}, [queryString, productList]);
I know that productList changes every render, and that's probably why it isn't working. But I didn't figure out how can I solve the problem. I've seen other problems here and solutions with useReducer, but I none of them seemed to help me.
The error is this one below:
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
what you are doing here is fetching a product list and filtering it based on the query string and using that filtered list to render the UI. So ideally your filteredList is just a derived state based on your queryString and productList. So you can remove the filterProducts from your useEffect and move it outside. So that it runs when ever there is a change in the state.
function filterProducts (productName = '', productList = []) {
return productName.trim().length > 0 ? productList.filter((prod)=> {
return prod.title === productName;
}); : productList
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [queryString, setQueryString] = useState('');
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
}
}, [queryString]);
// query products is the derived state
const queryProducts = filterProducts(queryString, productList);
// Now instead of using productList to render something use the queryProducts
return (
{queryProducts.map(() => {
.....
})}
)
If you want the filterProducts to run only on change in queryString or productList then you can wrap it in useMemo
const queryProducts = React.useMemo(() => filterProducts(queryString, productList), [queryString, productList]);
When you use a setState function in a useEffect hook while having the state for that setState function as one of the useEffect hook's dependencies, you'll get this recursive effect where you end up infinitely re-rendering your component.
So, first of all we have to remove productList from the useEffect. Then, we can use a function to update your state instead of a stale update (like what you're doing in your example).
function filterProducts (productName, productList) {
const queryProducts = productList.filter((prod)=> {
return prod.title === productName;
});
return queryProducts;
}
function HomePage () {
const [productList, setProductList] = useState([]);
const [popupTrigger, setPopupTrigger] = useState('');
const [productDeleteId, setProductDeleteId] = useState('');
const [queryString, setQueryString] = useState('');
let history = useHistory();
useEffect(() => {
if (queryString.trim() === "") {
Axios.get("http://localhost:3001/api/product/get-all").then((data) => {
setProductList(data.data);
});
return;
}
setProductList(prevProductList => {
return filterProducts(queryString, prevProductList)
});
}, [queryString]);
Now, you still get access to productList for your filter, but you won't have to include it in your dependencies, which should take care of the infinite re-rendering.
I recommend several code changes.
I would separate the state that immediately reflects the user input at all times from the state that represents the query that is send to the backend. And I would add a debounce between the two states. Something like this:
const [query, setQuery] = useState('');
const [userInput, setUserInput] = useState('');
useDebounce(userInput, setQuery, 750);
I would split up the raw data that was returned from the backend and the filtered data which is just derived from it
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
I would split up the useEffect and not mix different concerns all into one (there is no rule that you cannot have multiple useEffect)
useEffect(() => {
if (query.trim() === '') {
Axios
.get("http://localhost:3001/api/product/get-all")
.then((data) => { setProducts(data.data) });
}
}, [query]);
useEffect(
() => setFilteredProducts(filterProducts(userInput, products)),
[userInput, products]
);
import React, {useState} from "react"
const App = () => {
const {loggedIn, setLoggedIn} = useState(false)
const handleClick = () => {
setLoggedIn(!loggedIn)
}
return (
<div>
<h1>You are {loggedIn ? 'logged in' : 'logged out'}</h1>
<button onClick={handleClick}>{loggedIn ? 'Log Out' : 'Log In'}</button>
</div>
)
}
export default App
I was writing some code using hooks, and when I click on the button, nothing happens and console shows unknown error message.
I tried changing it to:
() => handleClick
handleClick()
but they all don't work.
What is wrong with the code?
The problem is useState is returning with [] instead of {}.
You should have the following instead:
const [loggedIn, setLoggedIn] = useState(false);
+1 suggestion:
Also it is better to use the callback option when using setLoggedIn in order to capture the previous version of the state as the following:
const handleClick = () => {
setLoggedIn(prev => !prev);
}
I hope this helps!
change const {loggedIn, setLoggedIn} = useState(false)
To : const [loggedIn, setLoggedIn] = useState(false)
Dont use {} to declare useState variable and its setter function use [] these instead.
You are destructuring the state value and change handler incorrectly. It returns a tuple so you need to get the values like this:
const [loggedIn, setLoggedIn] = useState(false)