Component state and hooks not updated after browser previous button visit - reactjs

When doing a window.location.href (when submitSuccess is true) and navigate back with the browser previous button, my component does not re-render. So the submitSuccess and submitting state variables are still on true, while I need them to be false again when revisiting the application without a refresh of the page.
const MultipleStepForm = () => {
const [submitting, setSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
useEffect(() => {
console.log("Running!");
},[]);
useEffect(() => {
successFunction();
}, [submitSuccess]);
const successFunction = () => {
window.location.href = "https://www.example.com/other-page/";
};
return (
<div>
{(submitting) && (
<div>Submitting the form..</div>
) || (
<div><!-- Form elements in here --></div>
)}
</div>
);
});

When you call successFunction using window.location the URL is redirected to your webpage too or another domain? Why it's important, React is a single page application(SPA) so if you wanna change a page in the same domain or webpage I recommend you use a router, React has react-router or react-router-dom.
React Router Dom

Related

How to reset channel of window.echo on changing of route reactjs

I am using laravel-websockets to listen to event. I have no issue on the back-end side; The issue is on the front-end side.
SCENARIO:
When I go to a specific route post/[slug], the current channel is based on the current slug. When I redirect to the same route but different value of slug, the channel listens to the first value on page refresh and not to the current one.
const Component = () => {
const router = useRouter();
const {slug} = router.query;
useEffect(() => {
window.Echo.private(`post.${slug}`).listen('PrivateEvent', e => {
console.log(e)
});
}, [slug])
}
Example:
On page refresh, go to post/first-slug. Next, click to <Link to="/post/second-slug">About</Link>
The example above should listen to second-slug and not the first-slug.
How can I solve this without hard refresh or <a> tag?
You forgot to stop listening on the previous channel, so the events are still received. I suppose that you end up with two active channels, receiving events for both.
Inside a useEffect() you should return a cleanup function that clears resources created for the effect
Here is how:
const Component = () => {
const router = useRouter();
const {slug} = router.query;
useEffect(() => {
window.Echo.private(`post.${slug}`).listen('PrivateEvent', e => {
console.log(e)
});
return () => window.Echo.private(`post.${slug}`).stopListening('PrivateEvent');
}, [slug])
}
If this does not solve your problem, please:
display the slug in your component (return <div>slug</div>;) to confirm that the navigation really happens ;
show us the whole console log.

How to call useNavigate inside useEffect? - For bottom navigation in Ant Design - Mobile

I am new to React. I am making an app using AntD-mobile. For Bottom navigation, I am making use of the tabBar Component in AntD-mobile. I was not sure how to use Link for routing with TabBar and after a lot of failed attempts, came upon useNavigate.
const [navLink, setNavLink] = React.useState("/");
const navigate = useNavigate();
const redirect = useCallback(() => navigate(navLink, {replace: true}), [navigate]);
I want to redirect to whichever tab is clicked upon, for that, I used State, TabBar's onChange changes the state first and calls redirect. But for some reason, the previous state is loaded. If I am on state 1 and click 2, it stays on 1, When I click 4, it goes to 2 and so on. So probably, the redirect is loading ahead of state change. -
<TabBar onChange={(key)=>{setNavLink(key); redirect();}}>
{tabs.map(item => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title}>
</TabBar.Item>
))}
</TabBar>
To solve, this I tried using useEffect(), wherein I change only the state in TabBar's onChange and call redirect inside useEffect. But this doesn't work. redirect is not working.
useEffect(() => {
redirect()
}, [navLink]);
What am I doing wrong? How to set bottom navigation in the tabBar?
Issue
The original code didn't work because both setNavLink(key); and redirect(); happen in the same render cycle. When redirect is called the enqueued state update hasn't been processed yet, so it's still the previous state's value.
onChange={(key) => {
setNavLink(key); // <-- enqueued state update
redirect(); // <-- navLink state not updated yet!
}}
Solutions
The redirect function is missing a dependency, adding navLink to the dependency array will re-enclose the updated navLink state value. useCallback(() => navigate(navLink, {replace: true}), [navigate]); only computes/recomputes the callback on the initial render or when the dependencies update.
const redirect = useCallback(
() => navigate(navLink, { replace: true }),
[navigate, navLink]
);
useEffect(() => {
redirect()
}, [redirect]);
...
onChange={setNavLink}
You can pass the navLink state in as an argument.
const redirect = useCallback(
(navLink) => navigate(navLink, { replace: true }),
[navigate]
);
useEffect(() => {
redirect(navLink);
}, [navLink, redirect]);
...
onChange={setNavLink}
Or you can just use navigate directly in the useEffect when the navLink state updates.
useEffect(() => {
navigate(navLink, { replace: true });
}, [navLink]);
...
onChange={setNavLink}
Or just navigate directly in the onChange handler.
<TabBar onChange={(navLink) => navigate(navLink, { replace: true })}>
{tabs.map(item => (
<TabBar.Item
key={item.key}
icon={item.icon}
title={item.title}
/>
))}
</TabBar>

How to detect route changes using React Router in React?

I have a search component that is global to my application, and displays search results right at the top. Once the user does any sort of navigation, e.g., clicking a search result or using the back button on the browser, I want to reset the search: clear the results and the search input field.
Currently, I am handling this with a Context; I have a SearchContext.Provider that broadcasts the resetSearch function, and wherever I am handling navigation, I have consumers that invoke resetSearch before processing navigation (which I do programmatically with the useHistory hook).
This doesn't work for back button presses on the browser's controls (since that is something out of my control).
Is there an intermediate step before my Routes are rendered (or any browser navigation happens) that I can hook into, to make sure my resetSearch function is invoked?
As requested, here is the code:
// App.js
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const resetSearch = () => {
setResults([]);
setQuery("");
};
<SearchContext.Provider value={{ resetSearch }}>
// all of my <Route /> components in this block
</SearchContext.Provider>
// BusRoute.js
const { resetSearch } = useContext(SearchContext);
const handleClick = stop => {
resetSearch();
history.push(`/stops/${stop}`);
};
return (
// some other JSX
<button onClick={() => handleClick(stop.code)}>{stop.name}</button>
);
You can listen to history changes:
useEffect(() => {
const unlisten = history.listen((location) => {
console.log('new location: ', location)
// do your magic things here
// reset the search: clear the results and the search input field
})
return function cleanup() {
unlisten()
}
}, [])
You can use this effect in your parent component which controls your global search's value.
You can use componentWillUnmount feature from class components with useEffect in hooks with functional component

Redirect shows the sign in page partially before redirecting in next js react

I have a simple app where if a user is signed in and if the user manually tries to go to /signin page he is redirected to the index page. I'm using nextjs and to achieve this is I run a function in useEffect where boolean is checked true if so I use Router.push is used to redirect the user to the index page. The functionality works fine I see a few milliseconds of sign-in page before redirecting to the index page. Why is that? is it because the useEffect is called every time after the component is rendered? basically I want to run that code before rendering component and useEffect runs after the component is rendered. I want basically to run the code something like componentWillMount. How do I do it in the functional component? or is there any other solution?
const SigninComponent = () => {
useEffect(() => {
isAuth() && Router.push(`/`);
}, []);
return(
// components
);
}
Thats because useEffect will only run after component has mounted.
You can use different solutions :
Conditional rendering, add a loading field to state that is true by default, when it is true your component will render something like a spinner/loading component, if isAuth() return false then you will render something else (most likely a login form).
Pseudocode:
const SigninComponent = () => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if(isAuth()){
Router.push(`/`);
}else{
setLoading(false)
}
}, []);
if(!loading){
return <Login />
}else{
return <MyloaderComponent />
}
}
Use HOC component, similar to above but you will wrap the above logic in a HOC component
Use getServerSideProps and run isAuth() server-side instead that client-side, if you redirect inside getServerSideProps the component will not render at all. (you can use getServerSideProps only in pages)
export const getServerSideProps = async (ctx) => {
const auth = await isAuth() // or other any logic, like read cookies...
if (!auth) {
const { res } = ctx;
res.setHeader("location", "/");
res.statusCode = 302;
res.end();
return;
}
return {
props: {}, // or return user
};
};
You may consider using Redirect Component
const SigninComponent = () => {
if (isAuth()) return <Redirect to="/" />
return(
// components
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Fetching w/ custom React hook based on router parameters

I'm trying to fetch data with a custom React hook, based on the current router parameters.
https://stackblitz.com/edit/react-fetch-router
What it should do:
On first load, check if URL contains an ID...
If it does, fetch a todo with that ID
If it does not, fetch a todo with a random ID & add ID to url
On fetch button clicks...
Fetch a todo with a random ID & add ID to url
What is wrong:
Watching the console or inspector network tab, you can see that it's firing several fetch requests on each click - why is this and how should this be done correctly?
Since you used history.push on handleClick, you will see multiple requests being sent as you are using history.push on click handler which will cause a re-render and make use use random generated url as well as also trigger the fetchTodo function.
Now a re-render will occur which is cause a randomised id to be generated. and passed onto useTodo hook which will lead to the fetchTodo function being called again.
The correct solution for you is to set the random todo id param on handleClick and also avoid unnecessary url updates
const Todos = () => {
const history = useHistory();
const { id: todoId } = useParams();
const { fetchTodo, todo, isFetching, error } = useTodo(todoId);
const isInitialRender = useRef(true);
const handleClick = () => {
const todoId = randomMax(100);
history.push(`/${todoId}`);
};
useEffect(() => {
history.push(`/${todoId}`);
}, []);
useEffect(() => {
if(!isInitialRender.current) {
fetchTodo();
} else {
isInitialRender.current = false
}
}, [todoId])
return (
<>
<button onClick={handleClick} style={{marginBottom: "10px"}}>Fetch a todo</button>
{isFetching ? (
<p>Fetching...</p>
) : (
<Todo todo={todo} color={todo.color} isFetching={isFetching} />
)
}
</>
);
};
export default Todos;
Working demo

Resources