Every Time i try to refresh the page it returns to 0.
I'm taking the bestScore from turns when the match is equal to 6,
so basically everytime the matched cards hit 6 it will take the bestScore from the turns and save the bestScore to localStoarge and it works but when i try to refresh its gone
function App() {
const [cards, setCards] = useState([]);
const [turns, setTurns] = useState(0);
const [match, matchedCards] = useState(0);
const [bestScore, setBestScore] = useState(
localStorage.getItem("highestScoresss")
);
const [choiceOne, setChoiceOne] = useState(null); //Kullanici 1.karta basinca setChoiceOne o karti alacak ve guncelliyecek
const [choiceTwo, setChoiceTwo] = useState(null); //Kullanici 2.karta basinca setChoiceTwo o karti alacak ve guncelliyecek
const [disabled, setDisabled] = useState(false);
useEffect(() => {
if (match === 6) {
const highScore = Math.min(turns, bestScore);
setBestScore(highScore);
setBestScore(turns);
} else {
console.log("false");
}
}, [turns]);
useEffect(() => {
localStorage.setItem("highestScoresss", JSON.stringify(bestScore));
});
This Is the JSX
<div className="bilgi">
<p>Sıra: {turns}</p>
<p>Bulunan: {match}</p>
<p>En iyi Skor: {bestScore}</p>
<button onClick={shuffleCards}>Yeni Oyun</button>
</div>
</div>
The issue with your implementation is that you set state to 0 first, and then the useEffect hook runs and sets localStorage to the state value.
If you are potentially initializing your state to a value stored in localStorage then I suggest using a lazy initialization function so the initial state value is set before the initial render and eliminates the need for the additional useEffect hook to set state from storage. This reads from localStorage and returns the parsed value, or 0 if the parsed result is null or undefined.
const initializeState = () => {
return JSON.parse(localStorage.getItem("highestScoresss")) ?? 0;
};
...
const [bestScore, setBestScore] = useState(initializeState());
You will want to use a dependency array on the useEffect that is persisting the "highestScoresss" value in localStorage such that it only triggers when the bestScore state value updates and not on each and every render.
useEffect(() => {
localStorage.setItem("highestScoresss", JSON.stringify(bestScore));
}, [bestScore]);
After looking at the Code image, I think that you want that the bestScore to be set in the local storage with the key highestScores.
Your current useEffect hook implementation lacks a dependency array. You want that the localStorage should be updated every time a new bestScore is set.
For that add bestScore to the dependency array.
useEffect(() => /* YOUR OPERATION*/, [any_dependency])
Also, I recommend that you look at your first useEffect implementation again. You seem to be setting the bestScore state twice. Once with highScore and then with turns.
Recommended Reading
About Dependency Array - ReactJS Docs
Related
I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
I have a filter on the drawer and want to reset state when the drawer is closed. There are filters by number of people, type of activity or input value.
This is the local state.
const [searchQuery, setSearchQuery] = useState<string>("");
const [count, setCount] = useState<number>(1);
const [checkedFilters, setCheckedFilters] = useState<string[]>([]);
This is the filter.
const activities = useMemo(() => {
return allActivities.filter(
(item) =>
item.participants >= count &&
item.activity.toLowerCase().includes(searchQuery)
);
}, [count, searchQuery, allActivities]);
To make the already given answers a bit more complete, if you want to reset all filters at once, I suggest to add a function as such:
function resetAllFilters() {
setSearchQuery("");
setCount(1);
setCheckedFilters([]);
}
You can now call resetAllFilters() when you want to reset all the filters, like so:
<button onClick={resetAllFilters}/>Clear</button>
And in your case, you can use the function in the same way as event that is called when the drawer closes.
To resetting state, you just need to set it back to empty or your predefined state:
setSearchQuery("");
AND/OR
setCheckedFilters([]);
AND/OR
setCount(1);
To know how to reset state, first you need to know what is the initial value for each and every state
const [searchQuery, setSearchQuery] = useState<string>(""); // initial value is ""
const [count, setCount] = useState<number>(1); // initial value is 1
const [checkedFilters, setCheckedFilters] = useState<string[]>([]); // initial value is []
After knowing the initial value, you just need to invoke setState with initial value as below
setSearchQuery("");
setCount(1);
setCheckedFilters([])
i am working on eCommerce project , i am currently facing a problem where as user clicks on add to cart button the code setCart([...cart,item]) runs since the initial state of cart is empty array like const [cart,setCart] = useState([]) therefore as i try to save it on localstorage it gives me empty array and then start saving data in second array
code:
const [cartValue,setCartValue] = useState(0)
const [cart,setCart] = useState([])
const cartValueIncreaserandSetter = (item) =>{
setCart([...cart,item])
setCartValue(cartValue+1)
localStorage.setItem("items",JSON.stringify(cart))
}
i want the localstorage to immediately save the first item added but the localstorage gives [] array on first click on add to cart
When you have side effect in your code (which is the case when you manipulate the localstorage) you should use the useEffect hook.
I suggest you to rewrite your code like this :
const [cartValue,setCartValue] = useState(0)
const [cart,setCart] = useState([])
const cartValueIncreaserandSetter = (item) => {
setCart([...cart, item])
setCartValue(cartValue+1)
}
useEffect(() => {
localStorage.setItem("items",JSON.stringify(cart))
}, [cart]);
That happens because the useState hook is asynchronous.
try using the existing cart and append the item (which came as a parameter) like this:
localStorage.setItem("items",JSON.stringify([...cart,item]))
useEffect(() => {
if (cart.length > 0) localStorage.setItem("items", JSON.stringify(cart));
}, [cart]);
I have an example like this:
codesandebox
I want to modify a state value in a callback, then use the new state value to modify another state.
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("0");
const [added, setAdded] = useState(false);
const aNotWorkingHandler = useCallback(
e => {
console.log("clicked");
setCount(a => ++a);
setText(count.toString());
},
[count, setCount, setText]
);
const btnRef = useRef(null);
useEffect(() => {
if (!added && btnRef.current) {
btnRef.current.addEventListener("click", aNotWorkingHandler);
setAdded(true);
}
}, [added, aNotWorkingHandler]);
return <button ref={btnRef}> + 1 </button>
However, after this handler got called, count has been successfully increased, but text hasn't.
Can you guys help me to understand why this happened? and how to avoid it cleanly?
Thank you!
If count and state are always supposed to be in lockstep, just with one being a number and one being a string, then i think it's a mistake to have two state variables. Instead, just have one, and derive the other value from it:
const [count, setCount] = useState(0);
const text = "" + count;
const [added, setAdded] = useState(false);
const aNotWorkingHandler = useCallback(
e => {
setCount(a => ++a);
},
[]
);
In the above useCallback, i have an empty dependency array. This is because the only thing that's being used in the callback is setCount. React guarantees that state setters have stable references, so it's impossible for setCount to change, and thus no need to list it as a dependency.
There are few things causing the issue.
Setter does not update the count value immediately. Instead it "schedules" the component to re-render with the new count value returned from the useState hook. When the setText setter is called, the count is not updated yet, because the component didn't have chance to re-render in the mean time. It will happen some time after the handler is finished.
setCount(a => ++a); // <-- this updates the count after re-render
setText(count.toString()); // <-- count is not incremented here yet
You are calling addEventListener only once and it remembers the first value of count. It is good you have aNotWorkingHandler in the dependencies - the onEffect is being re-run when new count and thus new handler function comes. But your added flag prevents the addEventListener from being called then. The button stores only the first version of the handler function. The one with count === 0 closured in it.
useEffect(() => {
if (!added && btnRef.current) { // <-- this prevents the event handler from being updated
btnRef.current.addEventListener("click", aNotWorkingHandler); // <-- this is called only once with the first instance of aNotWorkingHandler
setAdded(true);
} else {
console.log("New event handler arrived, but we ignored it.");
}
}, [added, aNotWorkingHandler]); // <-- this correctly causes the effect to re-run when the callback changes
Just removing the added flag would, of course, cause all the handlers to pile up. Instead, just use onClick which correctly adds and removes the event handler for you.
<button onClick={aNotWorkingHandler} />
In order to update a value based on another value, I'd probably use something like this (but it smells of infinite loop to me):
useEffect(
() => {
setText(count.toString());
},
[count]
);
Or compute the value first, then update the states:
const aNotWorkingHandler = useCallback(
(e) => {
const newCount = count + 1;
setCount(newCount);
setText(newCount.toString());
},
[count]
);
I agree with #nicholas-tower, that if the other value does not need to be explicitly set and is always computed from the first one, it should be just computed as the component re-renders. I think his answer is correct, hope this context answers it for other people getting here.
I have this component, that needs to fetch data, set it to state and then pass it to the children.
Some of the data also needs to be set in context.
My problem is that using useEffect, once called the API, it will re-render for each setvalue() function I need to execute.
I have tried passing to useEffect an empty [] array, still getting the same number of re-renders, due to the fact that the state is changing.
At the moment the array is containg the set...functions to prevent eslint to throw warnings.
Is there a better way to avoid this many re-renders ?
const Home = (props) => {
console.log("TCL: Home -> props", props);
const classes = useStyles();
const [value, setValue] = React.useState(0);
//CONTEXT
const { listSavedJobs, setListSavedJobs, setIsFullView} = useContext(HomeContext);
const {
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
} = useContext(UserContext);
// STATE
const [searchSettings, setSearchSettings] = useState([]);
const [oppData, setOppData] = useState([]);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChangeIndex = index => {
setValue(index);
};
//API CALLS
useEffect(() => {
const triggerAPI = async () => {
setIsFullView(false);
const oppResponse = await API.getOpportunity();
if(oppResponse){
setOppData(oppResponse.response);
}
const profileResponse = await API.getUserProfile();
if(profileResponse){
setUserName(profileResponse.response.first_name);
setUserLastName(profileResponse.response.last_name);
setUserEmail(profileResponse.response.emailId);
}
const profileExtData = await API.getUserProfileExt();
if(profileExtData){
setAvatarProfile(profileExtData.response.avatar);
setListSavedJobs(profileExtData.response.savedJobs);
setSearchSettings(profileExtData.response.preferredIndustry);
}
};
triggerAPI();
}, [
setOppData,
setUserName,
setUserLastName,
setUserEmail,
setAvatarProfile,
setListSavedJobs,
setIsFullView,
]);
...```
Pass just an empty array to second parameter of useEffect.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.
Source
Edit: Try this to avoid rerenders. Use with caution
Only Run on Mount and Unmount
You can pass the special value of empty array [] as a way of saying “only run on mount and unmount”. So if we changed our component above to call useEffect like this:
useEffect(() => {
console.log('mounted');
return () => console.log('unmounting...');
}, [])
Then it will print “mounted” after the initial render, remain silent throughout its life, and print “unmounting…” on its way out.
Prevent useEffect From Running Every Render
If you want your effects to run less often, you can provide a second argument – an array of values. Think of them as the dependencies for that effect. If one of the dependencies has changed since the last time, the effect will run again. (It will also still run after the initial render)
const [value, setValue] = useState('initial');
useEffect(() => {
// This effect uses the `value` variable,
// so it "depends on" `value`.
console.log(value);
}, [value])
For more clarification useEffect
If you are using React 18, this won't be a problem anymore as the new auto batching feature: https://reactjs.org/blog/2022/03/29/react-v18.html#new-feature-automatic-batching
If you are using an old version, can refer to this solution: https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html