useContext value not updating - reactjs

I was trying to use useContext to change the authentication state when a user logs in and this would be received in other components to conditionally show based on whether the user has logged in.
Login Context was setup as below
I passed the provider in this way
I made a button which upon clicking sets loggedIn as true
However, when I re-route to store, the value from useContext still stays as false although I have updated it in the Login component
Upon routing back to "/login" the state from useContext still shows false rather than true
Am I missing something wrt how useContext works?

That's expected. You dispatch your action, but the resulting state will be visible only on the next render phase. Not immediately. It will not happen synchronously. If you want to inspect that value, you can:
React.useEffect(() => {
console.log('loggedIn value changed', state.loggedIn)
}, [state.loggedIn])

I managed to figure out what was happening from another post: useContext does not survive between pages
So, by using react-router-dom and Link the application now works and passes the context to different pages and components but does not survive upon refresh or on opening a new tab.
Note that if this feature is required, for example login/auth, the best way is through localStorage

Related

How to redirect to a particular component on a certain path

I have a component with a particular path, let call it bigComponent. Within the bigComponent there are a few smaller components that are switched to depending on the state of the bigComponent, we'll call them smallComponents. Inside one of the smallComponents there is a link to third formComponent which has onClose property, which trigers history.goBack. My goal is to redirect a user to the smallComponent. history.goBack is not currently a solution as the smallComponent doesn't have its own path. Right now, a user gets redirected to the bigComponent. Could you please direct me toward how I could redirect the user to the smallComponent instead? Probably, I need the bigComponent to be loaded with a certain state as a result of such redirect.
"Probably, I need the bigComponent to be loaded with a certain state as a result of such redirect."
Yes, that may works. Probably not the best practice I think, but quite practical.
To pass state while redirecting, you can use either history.push(path, [state]) or history.replace(path, [state]) where state is an object and can be accessed via location.state from useLocation. Then you can initialize the bigComponent's state using the value.

React Router - history being reverted after a render on a parent

I am storing some state in a context using useState, and using a useEffect in there to update history (via react-router-dom's useHistory).
After a user clicks a Cancel button, I clear these state values, and the useEffect then runs history.replace({search: ""}), emptying the query string.
However, if anything causes a re-render following that, the query string is restored with its previous value. This is despite the state in context being nulled out.
This happens without the history.push I use to update the query string in the first place, confirmed with debuggers, so it's happening somewhere outside of my code. It seems that, somehow, the router is jumping back in history one step on the render following my emptying the query string.
I'll try to write this in steps:
user interacts with a form
useState values in a context are bound to the inputs and update accordingly
useEffect watching those values uses history.push to update the query string (say /home?field1=foo)
user hits 'Cancel', which sets all of those state values to null
useEffect watching those values uses history.replace with {search: ""} (or {}), removing the query string from the URL (path is now /home)
user causes a re-render on an ancestor, and previous query string is restored to URL - /home?field1=foo
I'm at a loss, any help is much appreciated!
I haven't drilled down to the exact reason, but the problem here was that I was using useLocation and useHistory in multiple components, rendered at the same time. When search was updated, only the instance of useHistory that I called push or replace on would have the new state. The other instance would be left with the old state, and some other logic was causing it to use that false state to update the querystring again.
tl;dr if using useLocation and useHistory in multiple components, be careful as they are not aware of one another and not necessarily up to date as one would think.

How to keep updated react context value inside a callback in a custom hook

I have a userContext wrapped my app, I have a custom hook useAnalytics used to send analytics events and I have a Login component which performs the log in and changes the user value in the context and send an event.
My problem is when I do a setUser inside my Login component the updating user data is not reflected in useAnalytics hook which it sends always the user data in every event
Here a sandbox: https://codesandbox.io/s/romantic-perlman-wzxti?file=/src/Login.js
You can open the console and see the results after clicking SEND
I know this is probably an anti-pattern but it would be good to know why I get this behavior
Thanks!
The problem is that all set states are async, while the sending of the event is in sync. So you dispatch the change, send the analytic and after a timeout, the state is updated.
There are 2 main ways to fix this.
Set a timeout to wait for react to update the state and push the analytic after the state is updated:
setUser({ type: "login", data: { user: "pippo" } });
setTimeout(sendEvent, 16);
Send the analytic with a middleware:
Add a middleware to intercept all reducer changes and push those to the analytic without the hook itself. This will guarantee that you get all changes that you need to track by checking for the action.type and sending the new data from action.payload.

What's the best way of closing a modal after a request finishes?

Context: I want to create a modal which lets the user fill in a form, and submit it. The modal should close after the POST request finishes. There are multiple modals with similar behavior but different form content, so I need to make it reusable. The data in the form is not used anywhere else in the frontend app, and is only stored in the backend.
Relevant tech stack: react, redux, react-redux, redux-saga, no SSR
Option 1: Store modal & request state in redux
Opening and submitting the modal would trigger the following sequence of events:
Dispatch action to open modal
Dispatch action to submit form with data
Send POST request to submit form, set redux loading state to true
If success, set redux loading state to false, and "modal open" state to false
Concerns: Conceptually places a lot of functionality in redux, when that functionality is, in principle, specific to the modal itself.
Option 2: Store the request state in redux, but store modal state in the component, use useEffect to check when the request succeeds
Opening and submitting the modal would trigger the following sequence of events:
Open the modal, which changes some local "modalOpen" state to true
Dispatch action to submit form with data
Send POST request to submit form, set redux loading state to true
If success, set redux loading state to false
In the modal component, a useEffect hook or componentDidUpdate function checks if the state changed from e.g. {loading: true, success: false} to {loading: false, success: true}, and if so, set the local modalOpen state to false
Concerns: brittle, error prone, strong coupling between useEffect hook and redux state and how the state transitions from one into another.
Option 3: Submit the request in the component, don't use redux at all.
Opening and submitting the modal would trigger the following sequence of events:
Open the modal, which changes some local "modalOpen" state to true
Call fetch and submit the post request
In the .then() callback, check if the request succeeds. If it does, set the modalOpen state to false
Concerns: looks like an anti-pattern, because I'm calling fetch in the component itself.
What would your recommended approach be, and why?
I'd go with Option 3 Because It's the simplest and most straightforward way to go about it. The React Documentation Has an example of making an API call from within the a component so I don't think its an anti-pattern.
Your concern in option 1, is perfectly valid. Modal state should be localised to the Modal and not to the Global state stored in Redux. Option 2 is similar to option 1
Option 3 should be good per your context. It's okay to use fetch in the component itself since you only need it there. Only use redux or any global state management if you have data that is used across multiple components in your app.
I would choose option 1 if your application if sufficiently large since fetching data from store may appear in many pages. In this way, you can reuse the modal.
However, if the application is not very large, I may probably use option 3 because as #seyi_adel said, it may be more convenient and straight forward.

React context not being updated within callback

I am subscribing a user to events using pusher-js, and am having an issue with the information I am passing in the callback not staying updated. I use react router and have a 'location' context on my components.
connect() {
let {pusher, channelName, eventName} = this.props;
let pusher_channel = pusher.subscribe(channelName);
pusher_channel.bind(eventName, () => console.log(this.context.location.pathname));
}
For demonstration purposes I am just console logging the current url pathname the user is on when the event comes through. This component gets loaded and stays loaded when the user loads that application. The problem is that, if a user navigates to another page in the app and an event comes in, the pathname does not reflect the actual page the user is on but rather the page the user was on when they initially loaded the app.
Is there a way to keep the callback updated as to any context or prop changes that have occurred?
Updating Context
Don't do it.
React has an API to update context, but it is fundamentally broken and you should not use it.
Read more.
Use React Router's location descriptors instead.

Resources