I have a react native application using redux for state management.
On every API request, I have actions dispatched which is handled by reducer. This is then forwarded to saga and back to reducer.
I am using snackbar to show any errors which might have happened.
e.g. In the login flow, one of the operations is to fetch the OTP for username.
The action types are:
GET_OTP_FOR_USER_REQUEST
GET_OTP_FOR_USER_SUCCESS
GET_OTP_FOR_USER_FAILURE
The initial state for the reducer (LoginReducer) is
{
hasError: false,
error : {
display_message : "",
details : null
}
otherData : []
}
Now in case of GET_OTP_FOR_USER_FAILURE, the reducer will be updated to
{
hasError: true,
error : {
display_message : "Invalid Username",
details : null
}
otherData : []
}
In my view component, I conditionally render the snackbar based on the hasError flag
{this.props.hasError ? Snackbar.show({
title: this.props.error.display_message,
duration: Snackbar.LENGTH_LONG
}) : null}
If I don't reset the hasError, then the snackbar keeps coming for every setState call (which happens on textinputchange).
The current approach I am using is, I have an action to RESET_ERROR.
I call this action once the setState is called on the textbox on the component (in this case the username)
onUserNameTextChanged = (text) => {
this.props.resetError(); //Reset the error
this.setState({ //something });
}
The issue with this is that this.props.resetError(); will get called on every character update.
Just to ensure that I don't call render multiple time, I am using shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState){
if(this.props.hasError && (this.props.hasError === nextProps.hasError))
return false;
else
return true;
}
I am not sure if there is simpler or cleaner approach to handle these scenarios. Any directions will be helpful.
You can just conditionally call it if there's an error, that way you're not always invoking it:
onUserNameTextChanged = (text) => {
if (this.props.hasError) {
this.props.resetError(); //Reset the error
}
this.setState({ //something });
}
You can also tie it into the success action if you want that error to show until the API returns successfully.
Related
When an API returns a 401, I want to force logout the user. The following code is written inside a React Native Ignite project using Mobx
import { useStores } from "../../models"
this.apisauce.axiosInstance.interceptors.response.use(response => {
return response;
}, error => {
if (error.response.status === 401) {
useStores().authStore.reset()
}
return error;
})
The call to authStore.reset() doesn't happen, no error, no console.tron.log, just nothing. Is there a better practice to capture 401 and trigger a mobx action? Without handling it at each individual action that calls into the API?
I'm trying to save record into database, if this record is not in user's profile (he did not discover this place) but also this record exists in collection of all places.
I'm using Expo react native and I think my problem is, that if condition will execute before functions recordInUsersAccount and recordInGlobalDatabase . Is there any way how to ensure execution after these two functions are copleted? In functions I'm rewriting variables in state={}, so I can check them below. (I tried .then() and await, async but I was not succesful).
Thank you very much.
saveScannedQrCode(idOfPlace) {
this.recordInUsersAccount(idOfPlace);
this.recordInGlobalDatabase(idOfPlace);
if (!this.state.placeAlreadyScanned && this.state.placeExistInDatabase) {
// we will add this record into database
} else {
// we will NOT add this record into database
}
}
This is the code of functions:
recordInUsersAccount(idOfPlace) {
const userId = auth.currentUser.uid;
const usersRef = db
.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection("s53sKFeF5FS0DjuI2cdO1Rp9sCS2") // uid
.doc(idOfPlace); // id of place
usersRef.get().then((docSnapshot) => {
if (docSnapshot.exists) {
this.setState({
placeAlreadyScanned: true, // place is in user's database
});
} else {
this.setState({
placeAlreadyScanned: false, // place is NOT in user's database
});
}
});
}
recordInGlobalDatabase(idOfPlace) {
const usersRef = db
.collection("databaseOfPlaces") // default
.doc(idOfPlace); // id of place
usersRef.get().then((docSnapshot) => {
if (docSnapshot.exists) {
this.setState({
placeExistInDatabase: true, // place is in global database of places
});
} else {
this.setState({
placeExistInDatabase: false, // place is NOT in global database of places
});
}
});
}
The problem with the code is that setState in React is async, and you're trying to check the values straight after executing the functions which modify the state.
Assuming that your methods work fine and do what they're supposed to do, you could do something like:
Leave your methods as they are right now, modifying the state.
Call saveScannedQRCode as your are doing now, triggering both of the helper methods.
Instead of checking the state right after calling them, you could do that in the componentDidUpdate lifecycle hook.
componentDidUpdate(prevProps, prevState) {
if (prevState.placeExistInDatabase !== this.state.placeExistInDatabase && prevState.placeAlreadyScanned !== this.state.placeAlreadyScanned) {
// do something here - at this point state is already updated and ready to use.
// you could check for the values you're waiting for to update the DB, else not do anything.
}
}
saveScannedQrCode(idOfPlace) {
this.recordInUsersAccount(idOfPlace);
this.recordInGlobalDatabase(idOfPlace);
}
One thing - be sure to reset the state (e.g. set it to null) once you've processed the update, this way your componentDidUpdate hook won't have any problems and your strict equality will be fine.
Iam building an app using react hooks and apollo client 3
trying to update the state on useQuery complete
here is the code
const GET_POST_BY_ID_QUERY = gql`
query getPostById($postId: ID!) {
getPostById(postId: $postId) {
id
title
body
}
}
`;
const [{ title, body }, setPost] = useState({ title: '', body: '' });
useQuery(GET_POST_BY_ID_QUERY, {
variables: { postId: route?.params?.postId },
skip: !route.params,
onCompleted: data => {
console.log('data', data.getPostById);
setPost(data.getPostById);
},
onError: err => {
console.log(err);
}
});
it keep on giving me this error
Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function
I am not using useEffect at all within this screen.
What could be wrong ?
React is warning you that you're trying to update a stateful variable that's no longer there. What's probably happening is that your component is unmounted after your query has begun execution, but before it has actually completed. You can solve this by adding an if(mounted) statement inside your onCompleted handler to check if the component is still there, before trying to update its state.
However, I suggest you drop the onCompleted and onError callbacks and opt to the use variables as returned by the useQuery hook. Your code will look like this:
const { data, loading, error } = useQuery(GET_POST_BY_ID_QUERY, {
variables: { postId: route?.params?.postId },
skip: !route.params
})
if (loading) return 'Loading...';
if (error) return `Error! ${error.message}`;
return (
<div>
<p>{data.getPostById.title}</p>
<p>{data.getPostById.body}</p>
</div>
)
The new approach with hooks allows you to simplify your code and handle the lifecycle of your component without having to wire a bunch of event handlers together. This way you can avoid many of the state-woes altogether.
I am building an SPA and I have a couple of different forms that submit data to an API. I am using axios for the ajax calls and have built a wrapper class around it for my use-case, called api. Inside that class I handle errors thrown by that instance.
The problem is I was storing an instance of the api class in each form's state. I later realized that functions shouldn't live in the state due to serialization.
The reasoning behind having the api class in the state was so that all of the children components of a form could access the errors and display their respective error along with removing the error on update.
One solution could be using an axios interceptor and commit all errors to a global errors module. But then I wouldn't know which errors belong to which form, in case two forms (or other requests) were submitted at the same time. I could of course save the errors in regard to the request URI, but then I would also have to take the request method into consideration.
reportError(state, { uri, method, errors }) {
state.errors[uri + '#' + method] = errors;
}
Then I could have a getter like:
getErrorsByRequest: state => ({ uri, method }) => {
return ...
}
But this feels unnecessarily awkward.
Since what I am trying to achieve is most likely very common, I am wondering, how do I sanely handle ajax errors reusably with Vuex?
I was checking for my old projects, and i did something similar:
This is my axios instance interceptor:
axiosInstance.interceptors.response.use(response => response, error => {
const { status } = error.response
...
...
// data invalid (Unprocessable Entity)
if (status === 422) {
// errors list from response
const dataErrors = error.response.data.errors
let objErrors = {}
// joining just the first error from array errors as value for each prop
for (let key in dataErrors) {
objErrors[key] = dataErrors[key].join()
}
// commiting to errors module
store.commit('errors/SET_ERRORS', objErrors)
}
...
...
})
Here my store module errors:
export const state = {
form_errors: {}
}
export const mutations = {
SET_ERRORS: (state, errors) => { state.form_errors = errors },
CLEAN_ERRORS: (state) => { state.form_errors = {} }
}
Using on components:
computed: {
...mapState('errors', ['form_errors'])
}
Using on form template:
<q-input
v-model="form.description"
:error-message="form_errors.description"
:error="!!form_errors.description"
/>
I'm trying to find a way to execute a function upon termination of the app, what function can I use to do this in react native?
Basically, whenever the user closes the app (completely terminate).
This might help:
AppState.addEventListener('change', state => {
if (state === 'active') {
// do this
} else if (state === 'background') {
// do that
} else if (state === 'inactive') {
// do that other thing
}
});
You can use the AppState API provided by react-native in your root component where you register/mount your app.
There you can add an onChange eventlistener to AppState which executes your custom function whenever AppState.currentState.match(/inactive|background/) returns true. Your custom function however should be isolated from your other component states.