how can i get useState value inside useCallback (react hook) - reactjs

i can't get the useState value that inside of useCallback. when i run the code below,
import React, { useCallback, useEffect, useRef, useState } from 'react';
const DBLab = () => {
const [hashHistory, setHashHistory] = useState("initial_value");
const [inputValue, setInputValue] = useState("");
const didAuthMountRef = useRef(false);
const set = useCallback(() => {
const decodeHash = decodeURI(window.location.hash);
console.log();
console.log("decodeHash: " + decodeHash);
console.log("hashHistory: "+ hashHistory);
setHashHistory(decodeHash);
},[hashHistory]);
useEffect(() => {
const startFunc = async() => {
set();
window.addEventListener('hashchange', set);
didAuthMountRef.current = true;
}
if(!didAuthMountRef.current) {
startFunc();
}
}, [set]);
return (
<div>
<h1>dblab</h1>
<h1>{hashHistory}</h1>
<input type='text' value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
</div>
)
}
export default DBLab;
in web console, i get
decodeHash: #/dblab#first
hashHistory: initial_value
which is right. but when i change url to http://localhost:3000/#/dblab#next, i get
decodeHash: #/dblab#next
hashHistory: initial_value
this is wrong. because hashHistory has not changed. but it is not useState problem. because hashHistory that i see with <h1>{hashHistory}</h1> in screen is #/dblab#next which is the right hash.
how can i get the right hashHistory inside of useCallback?.
ps. i must have to use useCallback.

useEffect(() => {
const startFunc = () => {
if(!didAuthMountRef.current) {
set();
didAuthMountRef.current = true;
}
window.addEventListener('hashchange', set);
}
startFunc();
return ()=> {
window.removeEventListener('hashchange', set);
}
}, [set]);
Logic problem

Related

How do I trigger React UseEffect on only the first render?

In am using React and trying to trigger a function only once, when the page initially loads. Currently, the below code triggers the console message twice at page load.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(!count.current){
count.current = 1;
myFunc();
}
return () => { count.current = null; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}
I have read up on how React 18 intentionally double-renders elements, which is my reason for using useRef and for returning the cleanup function in the useEffect hook, but it still doesn't seem to work.
hi please make sure you didn't invoke TestRef componenet twice in your page!
for debug and find rerenders you can use react profiler extention on chrome then remove extra rerender by using momo and useMemo and useCallback
Finally got it to work by doing this. This appears to only run myFunc() once, on the initial rendering of the component.
import { useState, useEffect, useRef } from "react";
export default function TestRef(){
const [inputValue, setInputValue] = useState("");
const count = useRef(null);
const myFunc = () => {
console.log('Function Triggered');
}
useEffect(() => {
if(count.current == null){
myFunc();
}
return () => { count.current = 1; }
}, []);
return (
<>
<p>Page content</p>
</>
);
}

How should I fix eslint warning (React Hook useEffect has a missing dependency) and a loop caused by the warning?

In following codes, eslint will give a warning.
Line 24:6: React Hook useEffect has a missing dependency: 'fetchPosts'. Either include it or remove the dependency array react-hooks/exhaustive-deps
import { useState, useEffect } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
};
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I fixed the warning by adding fetchPosts. Then I followed eslint instructions using useCallback and adding variables used in fetchPosts to deps. This change causes a loop. How should I fix the loop and eslint warning?
import { useState, useEffect, useCallback } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = useCallback(async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}, [page, posts]);
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location, fetchPosts]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I highly recommend this article to really understand what's going on when you use the useEffect hook. It talks, among other things, about your exact problem and ways to solve it. That said, you should move the function inside the useEffect callback, something like:
export const TimelinePage = () => {
/* ... */
useEffect(() => {
if (location.pathname !== '/') return;
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}
fetchPosts();
}, [location]);
/* ... */
};

How to get useEffect to not re-render infinitely

I am trying to render this CardsContainerCopy component after making an AJAX call with Redux-thunk.
If I leave the dependencies array in useEffect empty, the component doesn't render at all.
If I add cartItems to the dependencies array, the components will render but the fetchItems function keeps being called infinitely.
Code:
import React, { useEffect, useState } from "react";
import SingleCard from "./SingleCard";
import { createServer } from "miragejs";
import axios from "axios";
import itemsData from "../../config/ItemsData";
import { useDispatch, useSelector } from "react-redux";
import { selectCartItems } from "./shopSlice";
let server = createServer();
server.get("/api/food", itemsData);
const fetchItems = async (dispatch) => {
const itemsData = await axios.get("/api/food");
dispatch({ type: "shop/fetchedItems", payload: itemsData.data });
};
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(() => {
dispatch(fetchItems);
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};
export default CardsContainerCopy;
Your useEffect function does create an infinite loop, as you're listening to cartItems changes which triggers dispatch again. To avoid infinite re render you can do something like this:
const CardsContainerCopy = () => {
const [items, setItems] = useState([]);
const dispatch = useDispatch();
const cartItems = useSelector(selectCartItems);
useEffect(()=>{
dispatch(fetchItems);
}, [])
useEffect(() => {
setItems(cartItems);
}, [cartItems]);
return (
<>
{items?.map((item, i) => {
return <SingleCard props={item} key={i} />;
})}
</>
);
};

Router.push makes page flash and changes url to localhost:3000/?

After pushing a route in NextJS the path seems to be valid for a split of a second http://localhost:3000/search?query=abc and then changes to http://localhost:3000/?. Not sure why this is happening.
I have tried it with both import Router from 'next/router' and import { useRouter } from 'next/router'. Same problem for both import types.
Here's my component and I use the route.push once user submits a search form.
import React, { useEffect, useState } from "react";
import Router from 'next/router';
const SearchInput = () => {
const [searchValue, setSearchValue] = useState("");
const [isSearching, setIsSearching] = useState(false);
const ref = useRef<HTMLInputElement>(null);
useEffect(() => {
if (isSearching) {
Router.push({
pathname: "/search",
query: { query: searchValue },
});
setIsSearching(false);
}
}, [isSearching, searchValue]);
const handleSearch = () => {
if (searchValue) {
setIsSearching(true);
}
};
return (
<form onSubmit={handleSearch}>
<input
value={searchValue}
onChange={(event) => setSearchValue(event.target.value)}
placeholder="Search"
/>
</form>
);
};
The default behavior of form submissions to refresh the browser and render a new HTML page.
You need to call e.preventDefault() inside handleSearch.
const handleSearch = (e) => {
e.preventDefault()
if (searchValue) {
setIsSearching(true);
}
};

How does react useEffect work with useState hook?

Can someone explain what am I'm doing wrong?
I have a react functional component, where I use useEffect hook to fetch some data from server and put that data to state value. Right after fetching data, at the same useHook I need to use that state value, but the value is clear for some reason. Take a look at my example, console has an empty string, but on the browser I can see that value.
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Link to codesandbox.
The useEffect hook will run after your component renders, and it will be re-run whenever one of the dependencies passed in the second argument's array changes.
In your effect, you are doing console.log(value) but in the dependency array you didn't pass value as a dependency. Thus, the effect only runs on mount (when value is still "") and never again.
By adding value to the dependency array, the effect will run on mount but also whenever value changes (which in a normal scenario you usually don't want to do, but that depends)
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
console.log(value);
};
fetchData();
}, [value]);
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;
Not sure exactly what you need to do, but if you need to do something with the returned value from your endpoint you should either do it with the endpoint returned value (instead of the one in the state) or handle the state value outside the hook
import "./styles.css";
import React, { useEffect, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
function fetchHello() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("Hello World");
}, 1000);
});
}
const handleSetValue = async () => {
const hello = await fetchHello();
// handle the returned value here
setValue(hello);
};
useEffect(() => {
const fetchData = async () => {
await handleSetValue();
};
fetchData();
}, []);
// Or handle the value stored in the state once is set
if(value) {
// do something
}
return (
<div className="App">
<h1>{value}</h1>
</div>
);
};
export default App;

Resources