Let's assume that we have an API call authenticates the user and then based on if a user is returned from the server or not, we either replaceState or do nothing, which might look something like this:
<Route component={App} onEnter={authUser} />
const onEnter = (nextState, replaceState) => {
if (isClient) {
AuthHelper.findCurrentUser()
.then(response => {
// This here doesn't work, `replaceState` doesn't change the page or URL, however I know for sure
// that the line is being called because if I put a `debugger` right before it,
// the `debugger` hits every time...
replaceState({nextPathname: nextState.location.pathname}, '/dashboard');
})
.catch(response => {});
// If we put the same line here, it redirects the user to the dashbaord.
// replaceState({nextPathname: nextState.location.pathname}, '/dashboard');
}
}
The only differentiating factor between replaceState working and not working is that one is called from within the then of a Promise, and one is called outside of the Promise resolution. Does the Promise affect replaceState from executing?
The syntax for asynchronous onEnter hooks is necessarily a bit difference, since we need a way to block the transition until the handler has completed running.
If your onEnter hook is asynchronous, then it needs to take a 3rd argument, e.g.
function onEnter(nextState, replaceState, callback) {
/* ... */
}
When you do this, you have to call callback() yourself when your enter hook has finished running. It's necessary for us to use this API to support people who e.g. aren't using
Related
I'm making a React Native app and use redux-toolkit for most data handling.
The REST API I'm accessing might return 428 Precondition required in some cases. This means that the user should confirm an action, enter an OTP code etc, before being able to complete the request. Then the same request should be repeated, with some headers that sets the needed data.
I'm in doubt about the smartest way to integrate this with the UI. My idea was something like this:
A thunk(or axios interceptor, not decided yet) should catch the 428 response code, and then trigger something in the app that makes the UI prompt the user, and when the user responds, it should move on and do the request again.
Ideally, I'd like to be able to call a function from the thunk/interceptor, which triggers a UI update, and returns a promise, which fullfils when the UI actions has been done.
Something like this in the thunk/interceptor
const myThunk = createAsyncThunk('createBooking', async (timestamp: string, thunkApi): boolean => {
try {
const response = await fetch('http://....', {method:'POST'})
if (response.status === 428) {
const userInput = await promptUser(response.someString)
await fetch('http://....', {method:'POST', headers: {'User-Input': userInput}})
} else {
const json = await response.json()
thunkApi.dispatch(actions.done(json))
}
}
catch(error) {
thunkApi.dispatch(actions.error(error))
}
})
And my component (which is of course wrapped in a provider) could have some props like this:
type Props {
onAdditionalUserInput(input: string): void
promptForAdditionalInput: string
}
I hope the example above clarifies my challenge, I'm looking for the smartest way to make this async promptUser() function that triggers the promptForAdditionalInput prop on my UI component, and resolves when my UI component calls its onAdditionalUserInput(...) prop.
Thanks in advance for any response :)
I'm using the Context API and React functional components with hooks. I have a functional component ProfileForm.js
At the top of this file I call useContext so I can get access to the current state (an array of profile objects).
const {state: {userProfiles}, addProfile, removeProfile, editProfile} = useContext(UserProfilesContext);
For this example I will focus on the function addProfile. When the user clicks the submit button, I want to add the new profile to the global state/context and I want to save the updated list to AsyncStorage.
Here is my handler:
const saveProfileHandler = async(profiles) = >
{
const {
firstName, lastName, userPhone, userEmail, userMonth, userDay, userYear,
userStreet, userCity, userState, userZip,
}
= formState.inputValues;
Keyboard.dismiss();
if (validateForm()) {
let month = userMonth;
let day = userDay;
if (month.length == = 1) {
month = `0 $ { userMonth }
`;
}
if (day.length == = 1) {
day = `0 $ { userDay }
`;
}
const profile = new UserProfile(
firstName.trim(),
lastName.trim(),
userPhone.trim(),
userEmail.trim(),
month.trim(),
day.trim(),
userYear.trim(),
userStreet.trim(),
userCity.trim(),
userState.trim(),
userZip.trim(), );
// add profile to Context object
addProfile(profile);
await saveUserProfilesToStorage([... profiles, profile ]);
navigation.pop();
}
};
When I call addProfile I update the global state/context, so I know that React will re-render my component. So, I have 2 questions really:
Is it unsafe to rely on the global state value that I just saved. I mean, can I use the updated context state and save that to AsyncStorage or will it not be updated yet and thus unreliable?
After I call addProfile does the rest of the function continue to run before re-rendering from the state update, or does addProfile cause the component to re-render before the rest of the function finishes? If it does re-render in the middle of the function call, when does the rest of the function execute?
Thanks in advance.
This is what I was able to learn. I'll put it up here for others that stumble upon it.
It is important to know that in React, setState() is an asynchronous function. The JavaScript engine is made up of the memory heap and the call stack. The call stack will run all synchronous functions. Along side the JavaScript engine are the Web APIs (provided by the browser) and an Event Loop (callback queue).
When a function is executed it is placed on the call stack and execution begins synchronously. If you call another function from inside the currently running function, the new function will get its own execution context. The current function will pause execution, the new function will execute to completion (assuming no new function calls inside its context) and return control to the first function which will continue execution.
Asynchronous Events
Asynchronous code runs within the Web APIs environment of the browser. This prevents the code from blocking the JavaScript thread/call stack. Callback functions for the asynchronous code are registered in the Web APIs environment. When the callback is ready to be executed it is placed in the callback queue. All callbacks, except those returned by promises, are queued up here.
The callbacks won't be executed until the call stack is empty. Then they will be executed in a FIFO (first in first out) order.
It is also important to know that callbacks from promises (then, catch) don't get put into the callback queue. They go into a micro tasks queue. This queue has priority over the callback queue. This means all of the callbacks from the micro tasks queue will be called before the callback queue's tasks.
In my case, the context update will occur after navigation.pop(); but since the context is global and I'm not updating the UI after the component has unmounted it should be okay.
If I'm wrong on anything I welcome corrections.
I'm developing an app using Vuejs and Vuex.
I've got a Vuex module called settings_operations. This module has the following action:
async changePassword ({ commit }, { password, id }) {
commit(CHANGE_PASSWORD_PROCESSING, { id })
const user = auth.currentUser
const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
if (changePasswordError) {
commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
} else {
commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
}
}
Edit: the to() is https://github.com/scopsy/await-to-js
With the following mutations:
[CHANGE_PASSWORD_PROCESSING] (state, { id }) {
state.push({
id,
status: 'processing'
})
},
[CHANGE_PASSWORD_ERROR] (state, { id, error }) {
state.push({
id,
error,
status: 'error'
})
}
And then, in the component I want to use this state slice:
computed: {
...mapState({
settings_operations: state => state.settings_operations
})
},
watch: {
settings_operations: {
handler (newSettings, oldSettings) {
console.log(newSettings)
},
deep: false
}
}
The problem is that when the changePassword action results in an error, the watch doesn't stop in the PROCESSING step, it goes directly to the ERROR moment so the array will be filled with 2 objects. It literally jumps the "processing" watching step.
A funny thing that happens is that if I add a setTimeout just like this:
async changePassword ({ commit }, { password, id }) {
commit(CHANGE_PASSWORD_PROCESSING, { id })
setTimeout(async () => {
const user = auth.currentUser
const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
if (changePasswordError) {
commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
} else {
commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
}
}, 500)
},
It works! The watch stops two times: the first tick displaying the array with the processing object and the second tick displaying the array with 2 objects; the processing one and the error one.
What am I missing here?
Edit:
I reproduced the problem here: https://codesandbox.io/s/m40jz26npp
This was the response given in Vue forums by a core team member:
Watchers are not run every time the underlying data changes. They are only run once on the next Tick if their watched data changed at least once.
your rejected Promise in the try block is only a microtask, it doesn’t
push execution to the next call stack (on which the watchers would be
run), so the error handling happens before the watchers are run.
additionally, when you mutat an object or array, the newValue and
oldValue in a deep watcher will be the same. See the docs:
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
and as a final sidenote, I’ve never seen anyone use an aray as the
root state of a module, I have no idea if that will work for vuex in
all possible circumstances. I certainly would not recommend doing
this.
Edit with a better and more complete answer from the same member:
Why watchers are asynchronous at all? Because in the vast majority of
use cases, watchers only need to react to the last synchrnous change
that was done. In most cases (in the context of a component), it would
be couterproductive to to react to every change since you would
re-trigger the same behaviour mutliple times even though in the end,
only the last state is the important one.
In other words: Running a watcher on each change by default would
probably lead to apps that burn a lot of CPU cycles doing useless
work. So watchers are implemented with an asynchronous queue that is
only flushed on nexTick. And we don’t allow duplicate watchers then
because the older instance of a watcher would apply to data that
doesn’t “exist” anymore in that state once the queue is flushed.
An important note would be that this only applies to synchronous
changes or those done within a microtask, i.e. in an immediatly
resolving or failing promise - it would, for example, not happen with
an ajax request.
Why are they implemented in a way that they are still not run after a
microtask (i.e. an immediatly resolved promise? That’s a bit more
coplicated to explain and requires a bit of history.
Originally, in Vue 2.0, Vue.nextTick was implemented as a microtask
itself, and the watcher queue is flushed on nextTick. That meant that
back then, a watcher watching a piece of data that was changed two
times, with a microtask (like a promise) in between, would indeed run
two times.
Then, around 2.4 I think, we discovered a problem with this
implementation and switched Vue.nextTick to a macroTask instead. under
this behaviour, both data chhanged would happen on the current call
stack’s microtaks queue, while the watcher queue would be flushed at
th beginning of the next call stack, wich means it will only run once.
We found a couple of new problems with this implementation that are
much more common than the original issue with microtasks, so we will
likely switch back to the microtask implementation in 2.6. Ugly, but
necessary.
So, this should do the trick for now:
await Vue.nextTick();
I'm using a xml parser react-native-xml2js in react native, but this "plugin" uses a specific function for parse the xml, I wasn't found a correct way to use "this" within the function, I've tried using bind() in the callback but doesn't work as expected when using bind it fills my variable moments later after executed, so I don't know how to use it, this is my code:
state = { artcicles: null }
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => {
parseString(response, function (err, result) {
this.setState({
articles: JSON.stringify(result.rss.channel[0].item)
})
console.log('RAW: ' + result.rss.channel[0].item);
console.log('THIS: ' + this.state.articles);
}.bind(this));
});
}
When calling this.state.articles in render() at beginning shows null but a second later it fills the articles variable but at that moment the app shows the error when I'm trying to access to the variable.
Any ideas?
Thanks.
I can help you observe something. In React, setState is asynchronous, so the code on the following line after setState will be executed immediately after the setState call is placed in the event loop.
Your true issue is that those console.logs are working perfect, you aren't crazy. They are just being executed before setState has completed.
The secret trick here is that setState accepts a second parameter which is a callback that will be executed after the state is updated. You could place all your following logic inside that statement.
Here is a sampler pack:
this.setState({ dogs: 350 }, () => { console.log('The state has been updated.') })
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
Cite: https://reactjs.org/docs/react-component.html
In yours, it could look like this:
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => parseString(response, (err, result) =>
this.setState({
articles: JSON.stringify(result.rss.channel[0].item),
}, () => {
console.log('RAW:', result.rss.channel[0].item)
console.log('THIS:', this.state.articles)
}));
);
}
Checkout the modification I did on the console.log. It too can accept multiple parameters.
A list of JavaScript objects to output. The string representations of each of these objects are appended together in the order listed and output. Please be warned that if you log objects in the latest versions of Chrome and Firefox what you get logged on the console is a reference to the object, which is not necessarily the 'value' of the object at the moment in time you call console.log(), but it is the value of the object at the moment you click it open.
Cite: https://developer.mozilla.org/en-US/docs/Web/API/Console/log
I like that definition additionally because it speaks to the asynchronous nature of live references. One function by itself can be synchronous, but due to the callstack and function queue, you can load up an infinite number of functions into the queue and they will complete in a random order based on how long each one takes to complete because only one passes through the callstack at a time, on the main thread. Of course, it seems random order to us, but it's actually the mathematically exact fastest path through all those functions, assuming they are all deterministic.
Fast forward to the code in your question, setState doesn't care to stop surrounding code from executing unless you explicitly tell it to. That's what the callback is for, if you need to run some bonus code.
While we are talking about setState, I should mention also that you can pass a function to it. Imagine that the second parameter callback is your method of looking into the future after setState. The opposite of that is looking into the past, which is where the functional setState comes in handy by giving you closure around the previous unit of time. The previous state also happens to be the current state of whatever you are updating.
Here is a sampler pack for that:
this.setState((prevState) => {
// hello I like cats.gif
// we could run some fascinating code here
// as long as we return an object
console.log('rad')
return {
articles: [ ...prevState.articles, { new: 1, article: true }],
}
})
It gives you a safe window to guarantee state integrity through your update. I showed that example there as spreading an Array into a new Array and appending it with an object to demonstrate similar to a real scenario you might need to refer to the current state as part of your operations.
In a real scenario, you might sharpen that up to this, which capitalizes on implicit return of an Object literal (requires fat arrow syntax):
this.setState(prevState => ({
articles: [ ...prevState.articles, { new: 1, article: true }],
})
Hopefully that helps us see the climate of what is happening. In React, it is important to undergo a formal change management process, so every time you are getting or setting data, you need to be careful who is reading or writing data and from where, like which functions and which part of the program. React's way of taming JavaScript is to try to force data to always flow unidirectionally, immutably, and deterministic.
It makes things easier to reason about if everything is flowing one way. That only works if you require immutability and prefer a deterministic system. It means most functions are written declaratively, so they declare what the state looks like at the start of a function, do stuff, then declare what the state is at the end of the function.
React makes you think you are writing mostly pure JavaScript, but really it is managing your state using a first in, first out technique to avoid race conditions when perhaps thousands of components are trying to write to the state at the same time. While the user is in the browser rolling their face across the keyboard triggering all kinds of events, and we must not block the main thread or else suffer poor UX.
A formal change management process means there is probably an official pattern that you should use every time you get or set data. Luckily, the patterns are usually what you would do if you were writing pure JavaScript. Reactive programming and immutability help tame the wild asynchronous concurrency gods.
Sorry, we are digressing a bit, but I had to do it for science.
TLDR,
it's very important what you are doing before, during, and after this.setState(). It's a special function, a class method on the Component Class. I hope I have helped us understand a couple of its secrets today.
You were seeking to perform two operations in one setState call. Normally, you only do one which is to set the state :) Your intended usage is fine. We do nest one additional dimension, but it's fine because you are just performing one more operation. I wouldn't recommend it if you were doing a chain of functions in the callback.
Notice the React documentation that states,
Generally we recommend using componentDidUpdate() for such logic instead.
The reason it says that is componentDidUpdate() is listening for state changes, so you can run logic there that is listening for certain conditions and then acting. It saves you from having to care about performing a second operation after setState at the callsite.
Imagine you did this.state.hasFetchedStuff = true inside your componentDidMount() and then had something like this in componentDidUpdate():
componentDidUpdate() {
if (this.state.hasFetchedStuff) {
this.triggerSomething()
}
}
That can free your componentDidMount from having to care about anything after getting the data, which is perhaps good decoupling and a nice separation of concerns.
I converted this post into a Medium article as well and added much more detail: https://medium.com/#agm1984/reacts-setstate-is-a-special-function-and-it-helps-with-asynchronous-concurrency-669eddbe3dd1
In the render() method you can use an inline If:
{this.state.articles && (
// Display articles
)}
When articles stops being null the element right after && will appear and while it's null, no errors will be thrown.
Instead of using null for the initial value you can use an empty array []. That way your articles state variable is never in a consistent state. Also, you can avoid having a manual bind in your callback and use arrow functions instead which will keep the current scope. This way you'll have the correct closure to use this.state.
parseString(response, (err, result) => {
this.setState({
articles: JSON.stringify(result.rss.channel[0].item)
})
});
This answer to some tricky
componentDidMount() {
fetch('http://example.com/rss.xml')
.then((response) => response.text())
.then((response) => {
parseString(response, function (err, result) {
this.setState({
articles: JSON.stringify(result.rss.channel[0].item)
})
console.log('RAW: ' + result.rss.channel[0].item);
setTimeout(() => {
console.log('THIS: ' + this.state.articles);
}, 1000);
}.bind(this));
});
}
I'm using a higher-order wrapper component as a root that accomplishes 2 things:
Verify auth (redirect to login or home)
Do the initial load to store once the auth is complete.
I'm finding it hard to do those 2 things in this one wrapper class because I can't find a way to do a one-time initial load trigger if the user is not authenticated(has no existing session)
So for example I trigger a load when there is a session with a callback:
componentWillMount: function() {
LoginStore.addChangeListener(this._onChange);
var authData = AuthAPIUtils.checkForSession();
if(authData !== null) {
WebAPIUtils.loadStores(this.onBootstrapComplete);
}
},
"this.onBootstrapComplete" is a callback that will change the wrapper state
onBootstrapComplete: function() {
console.log("5-the final callback was made - onBootstrapComplete");
//localStorage.setItem( 'gvLoggedIn', true ); This is set true in home
this.setState({
bootstrapComplete: true,
});
},
"this.state.bootstrapComplete" is passed down the child components to switch from a loading spinner to rendering the components
render: function(){
if(this.state.loggedIn) {
var childrenWithProps = React.Children.map(this.props.children,function(child) {
return React.cloneElement(child,{bootstrapComplete : this.state.bootstrapComplete})
},this);
return (
<div className="wrapper-container">
{childrenWithProps}
</div>
)
}
else {
return (
<div className="wrapper-container">
<Login />
</div>
)
}
But when there isn't a session this callback solution for a one-time trigger breaks down.
I looked hard for a solution and the best I've come up with is:
The wrapper can only listen to a "LoginStore" which should only trigger once when there is a login and logout and then use _onChange to check for a log in and trigger the loading then.
Create a handler in the wrapper class and pass it down to the Login class as a callback.
Maybe one of those solutions is just fine(let me know if so) but I wanted to make sure I'm not doing something fundamentally poor to bootstrap my app.
For me it looks like the good approach. Just one point maybe, AuthAPIUtils should be an action file (but maybe it is already) and manage the dispatching. Reading at your code I think you're already using it this way. Otherwise I think your approach
1.The wrapper can only listen to a "LoginStore" which should only trigger once when there is a login and logout and then use _onChange to check for a log in and trigger the loading then.
2.Create a handler in the wrapper class and pass it down to the Login class as a callback.
is good