I am building a Simple React App and I am using api to set state and I am using Link to get into detail page But When I go into another page from Link and press back button then It is setting state again. But I am trying to prevent setting state again when I go back to the page from back button.
App.js
function MainPage() {
const [blogs, setBlogs] = useStae([]);
useEffect(() => {
axios.get("/api/blogs/").then((res) => {
setBlogs(res.data.blogs);
}
}
return (
<>
{
blogs.map((res) => <>
<b>{res.title}
// It is showing about 40 blogs.
<Link to={`blog-detail/${res.id}`}>Blog Detail</Link>4
</>
}
</>
)
}
When I click on Blog Detail and press button in Browser (Chrome) then It is setting state again.
What I have tried ?
I have also thought about Using LocalStorage with Redux Store like :-
const saveToLocalStorage = (state) => {
try {
localStorage.setItem('state', JSON.stringify(state));
} catch (e) {
console.error(e);
}
};
But the Array was 40 in length then I thought Would it be efficient to store that big array (40 maybe 100(max)) in localStorage ?`
Then I cancelled it.
Using only localStorage without redux but It was localStorage after all.
I have tried many times but it is still setting state with makeing request to backend server.
Any help would be much Appreicated.
If you want to keep the MainPage component from running the effect each time it mounts then you'll need to Lift State Up to a common parent/ancestor component that remains mounted while the app's router is matching and rendering different routed content/pages/components.
You can move the blogs state up to the component rendering the routes so the data is fetched only once when mounted and lives for the duration of the component's life. Don't forget to include a dependency array for the useEffect hook!
Example:
const App = () => {
const [blogs, setBlogs] = useState([]);
useEffect(() => {
axios.get("/api/blogs/")
.then((res) => {
setBlogs(res.data.blogs);
});
}, []); // <-- empty dependency array, run effect once on mount
return (
<Routes>
<Route
path="/blog"
element={<MainPage blogs={blogs} />} // <-- pass blogs as prop
/>
<Route
path="/blog-detail/:id"
element={<BlogDetails blogs={blogs} />} // <-- pass blogs as prop
/>
</Routes>
);
};
...
function MainPage({ blogs }) { // <-- consume blogs as prop
return (
<>
{blogs.map((blog) => (
<React.Fragment key={blog.id}>
<b>{blog.title}</b>
// It is showing about 40 blogs.
<Link to={`blog-detail/${blog.id}`}>Blog Detail</Link>
</React.Fragment>
)}
</>
)
}
Switching/navigating between routes now will no longer trigger the useEffect hook to refetch the blogs data.
Related
I use the useEffect hook to dispatch the getQuestions function in order to get the data from the server
function App () {
const dispatch = useDispatch();
useEffect(() => {
dispatch(getQuestions());
}, [dispatch]);
return (
<Routes>
<Route exact path="/" element={<Layout/>}>
<Route path="repetition" element={<Repetition/>}/>
<Route path="family" element={<Family/>}/>
</Route>
</Routes>
);
}
The problem is that when I, for example, open the family link (which I declared in the App function), initially I get the data, but when I refresh the page, the data disappears.
I certainly understand that when the page is refreshed the parent App component is not rendered from this and I get an error, similar issues I have looked at in the forums where it was suggested to use withRouter which updates the parent component, but my version of react-router-dom does not supports withRouter, except that I don't want to downgrade my version of react-router-dom to use withRouter.
I would like to know if there is any way to fix this problem.
I tried the option that #Fallen suggested, i.e. I applied the useEffect hook in each child element and analyzed this approach in GoogleLighthouse, and I'm happy with the results.
Here is my final code in child component
function Family () {
const dispatch = useDispatch();
const questions = useSelector(state => state.QuestionsSlices.familyQuestions);
useEffect(() => {
dispatch(getFamilyQuestions());
}, [dispatch]);
return (
<>
{questions.data.map((item, idx) => (
<div key={idx}>
{ idx + 1 === questions.score && CheckQuestionsType(item, questions) }
</div>
))}
</>
);
}
In react-router, we cannot push the same route in useHisotry() and cause a re-render. E.g., if component App is showing on route https://localhost:3000 and I click the button Click Me!, it won't cause a re-render:
function App() {
const history = useHistory();
return (
<button onClick={() => {history.push('/')}}> Click Me! </button>
)
}
I want to achieve similar functionality, but I am unsure about the approach or what I am missing.
My current route looks like this: https://localhost:3000/user/1
I want to go to user/2 by clicking a button.
My code looks like the below:
<Route exact path="/user/:userId" component={User} />
function User() {
const history = useHistory();
return (
<button onClick={() => {history.push('/user/2')}}> Click Me! </button>
)
}
The above code changes the route but doesn't re-render the component. How can I fix this issue?
Thanks
I don't recommend using history for this case.
If you really need to, inside User component get userId parameter and react on that.
<Route exact path='/user/:userId' component={User} />
const User = () => {
const { userId } = useParams();
return (
<div>userId: { userId }</div>
);
}
export default User;
My advice is to upgrade to react router dom v6 and use useNavigate , tutorial here
once you import useNavigate from react-router-dom
let navigate = useNavigate();
and on your button you call this function on click passing your desired url
<button onClick={()=> navigate('/users/2')}
Your component's info wont change because you arent rendering anything dynamically in it, so you should grab the userid from the url, and then lets say display it. Check Docs
As the answer below, you can do it exactly as he said.
const { userId } = useParams();
return (
<div>userId: { userId }</div>
);
I'm making a small SNS app using React. (Gatsby.js)
What I want to do is to persist state in previous pages even when you go back with the browser. Like twitter or instagram, when you go to follow or follower page and visit more pages, you do not lose data when you go back. I can't find a single clue.
Is it something to do with history api or routing?
There is also a User page and it contains some links to following/follower pages in my app. When a user reaches on a page, I am fetching API in useEffect hook using url params then store it to global state(recoil), show on the component.
Here is a problem.
When I visit user page and move forward to follower page, then visit another user page from there and move to his follower page, when I go back with the browser, they don't remember the global state (of course) and it'll get fetch data again which shows loading. Tried to clean data when unmounting because it shows previous data when you go to other page. Couldn't find the bast practice.
Probably it's nothing to do with global states (Recoil), somehow window is remembering what was in the previous pages (and scroll position too)?
I'd appreciate any advice. Thank you.
React/GatsbyJS
Router/Reach-router
Route
...
<PrivateRoute path="/user/:userId" component={UserPage} />
<PrivateRoute path="/:userId/follower" component={FollowerPage} />
...
UserPage
const UserPage = () => {
const { userId } = useParams()
const user = useRecoilValue(userProfileViewData)
const loading = useRecoilValue(loadingUserPage)
...
useEffect(() => {
... // fetch api to get user info
...// store it in global state -> userProfileViewData
}, [])
if(loading) return <div>Loading</div>
return (
<div>
<div>{user.name}</div>
...
<div onClick={() => navigate('/app/' + userId + '/follower')}>Follower</div>
...
</div>
)
}
export default UserPage
FollowerPage
const FollowerPage = () => {
const { userId } = useParams()
const [loading, setLoading] = useState(true)
const followerData = useRecoilValue(followers)
...
useEffect(() => {
...// api calls to get followers
...// store it in global state -> followers
}, [])
useEffect(() => {
return () => {
.. // I have a function to clean data here
}
}, [])
if(loading) {
return <div>loading....</div>
}
return (
<div>
<div>Follower</div>
<div>
{followerData.map(user => (
<div key={`user.id}>
<div onClick={() => navigate(`/app/user/` + user.id)}>
{user.name}
</div>
</div>
))}
</div>
</div>
)
}
export default FollowerPage
Maybe you can use Redux to globally remember state of values
I have the following situation: In component <Home /> I have a component made my me, <AutocompleteSearch /> which displays a list of books, and when we click one book, it does an api call to fetch the details about clicked book then and dispatch that saves in the books state, the current book. My problem is that I have an listener in the <Home /> component that when is triggered it checks if some value is equal with the current book id, and for that I use useSelector to get the current book in the <Home />, but I get an error saying that currentBook is undefined, I printed the books state and it prints an empty object that is the initial state. The strange thing is that I have another component in my <Home />, called to which I pass as props the current book, and in BookProfile the current book prop is a fine object, fetched from server, so the state updates correct, but when I try to access the current book in the <Home /> it look like it never changed, and as well I wanna say, I tried to use the same useSelector in another child component of home, and it works here too, it logs in console a fine object from server, representing the last clicked book in the AutocompleteSearch.
I'll insert some code snippets:
const currentUser = useSelector(state => state.users.currentUser)
const currentBook = useSelector(state => state.book.currentBook)
return (
...........
{openSearchBar && (
<AutoCompleteSearch
suggestionsList={bookListForSearchBar}
elementClickAction={setMainContent}
/>
)}
...........
<BookProfile
book={currentBook}
user={currentUser}
/>
...
}
const bookListClicked = async book=> {
await dispatch(getCurrentBook(book.id))
}
const currentBookReceived = book=> ({
type: actions.CURRENT_BOOK_RECEIVED,
anime,
})
export default function News() {
const currentUser = useSelector(state => state.users.currentUser)
const currentBook = useSelector(state => state.book.currentBook)
return (
<>
<div
onClick={() => {
console.log(currentUser)
console.log(currentBook)
}}>
News
</div>
</>
)
}
In News components state works fine, it logs the correct book, but in Home it logs undefined.
let initialState={}
export default (state = initialState, action) => {
switch (action.type) {
case actions.CURRENT_BOOK_RECEIVED:
return { ...state, currentBook: action.book }
default:
return state
}
}
Also, currentUser is fetched from server after succes at login then saved in users state with dispatch, and I printed it in console in Home and it is as it should be, not undefined like currentBook.
I am new in front-end development and I don't understand very well how the things works, I don't understand why in one component the state is seen well and in another it is not.
I have three different Users. I would want them to access different routes as per their role. I set a token and userRole in the localstorage and I would like to have my protected Route check the avaliability of token in the localstorage which is retrieved by redux.
const TeacherRoute =({ component: Component , auth, ...rest}) => (
<Route
{...rest}
render = {props =>{
if(!props.token){
return <Redirect to="/login" />
}else if(props.token !== null){
if (props.userRole !== 'principal'){
if (props.userRole === 'student'){
return <Redirect to="/studentdashboard" />
}else if(props.userRole ==='teacher'){
return <Redirect to="/teacherdashboard" />
}else{
return <Redirect to="/login" />
}
}
}else {
return < Component {...props} />;
}
}}
/>
);
const mapStateToProps = state => ({
token: state.auth.token,
userRole: state.auth.userRole,
});
export default connect(mapStateToProps, )(TeacherRoute);
The challenge is that at first when I had the StudentRoute before adding the teacher and AdminRoute the route could redirect and disallow those who are not students to have access to the Student Route but the moment I added the other two Routes I got this error.
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.```
May you kindly help me fix this problem. Thanks in advance
This has been solved. Instead of delegating the role checking and token checking to the react Router we relied upon the Layout wrappers for all the dashboards. Since they control what is displayed in their children they were the perfect place to store our authentication Logic.