Handling 401 responses in a react-native functional component - react-native

I'm getting the following warning when an API request returns 401 and I send the user to the login screen:
Warning: Can't perform a React state update on an unmounted component.
What is the best way to handle this warning in a functional component that uses hooks. See the code below:
.
.
export default function MovieDetailsScreen() {
const [movie, setMovie] = useState({});
const movieId = useNavigationParam('movieId');
useEffect(() => {
// This is the method that does the request and returns 401 (It
// uses the fetch library)
Client.movies.show(movieId)
.then(result => {
setMovie(result)
})
}, [])
.
.
.

In general, warnings don't crash your application. But you should care about them. For instance, the previous warning(s) can lead to performance issues when not properly unmounting your stateful components
the request (e.g. Promise) isn't resolved yet, but you unmount the component. Then the request resolves, setMovie() is called to set the new state, but it hits an unmounted component.
You can try catch
Client.movies.show(movieId)
.then(result => {
setMovie(result)
})
.catch((err) => {
Client.movies.stop()
})

Related

Submitting Formik form in React Native

I am building a React Native app that uses Formik. When I submit the form I call handleSubmit
<Formik
onSubmit={values => {
handleSubmit(values)
}}>
I define this before the return on my form:
const handleSubmit = (values) => {
const { status, data } = usePostRequest("/api/holidays-request", {
dateFrom: "2023-02-01",
dateTo: "2023-02-28",
fromHalf: 0,
toHalf: 0,
});
};
I have hard coded some values here for testing.
My usePostRequest is a custom hook I wrote to actually send the data to my API.
When I submit my form then handleSubmit is triggered but I get an erorr:
Warning: An unhandled error was caught from submitForm() [Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
It must be reason 2 that I am failing but I am unsure how to use my usePostRequest to submit the values from the form.
I would recommend you take a look at the custom hooks documentation
In this particular case you should write the post logic as a function and use it here directly.
Do not call hooks in event handlers.
const handleSubmit = (values) => {
const { status, data } = usePostRequest("/api/holidays-request", {
dateFrom: "2023-02-01",
dateTo: "2023-02-28",
fromHalf: 0,
toHalf: 0,
});
};
See Invalid Hook Call Warning for details.

Rethrowing an error causes vue warning in jest test

I've got a vue application which exists of a page component and a nested child component, which makes a async post-request. If the request is failing, an error is thrown, which is propagated to the top component (Page.vue). This top component handles all errors of its child with the errorCaptured-Lifecycle hook. Here is an working example on stackblitz
The mechanism is working fine and no warning is displayed for my app. However, I want to test my child component isolated with jest and vue-test-utils. Therefore, I made a test:
import { mount } from "#vue/test-utils";
import Child from "./Child";
jest.mock("./api", () => ({
createSomething: jest.fn(),
}));
it("creation is erroneous", async () => {
const wrapper = mount(Child);
createSomething.mockImplementation(() => {
throw new Error("error");
});
let createAction = wrapper.find("[data-test-create]");
await createAction.trigger("click");
});
However, because the error is rethrown and never catched by the parent component, there is always a console log statement in my test output:
console.warn
[Vue warn]: Unhandled error during execution of native event handler
at <Child ref="VTU_COMPONENT" >
at <VTUROOT>
How can i solve this? I tried to wrap await createAction.trigger("click"); with try/catch but its not working. Do I need to mock the errorCaptured lifecycle of my parent?
One solution is to set a no-op app.config.errorHandler via the global.config mounting option:
const wrapper = mount(Child, {
global: {
config: {
errorHandler(err) { /* ignore */ },
},
},
})
demo

How to properly test if a Toast has been shown in react native using native base?

I am trying to write a test that checks if the screen is showing a Toast with an error message. The test passes, but there is a warning:
console.error
Warning: You called act(async () => ...) without await.
This could lead to unexpected testing behaviour, interleaving multiple act calls
and mixing their scopes. You should - await act(async () => ...);
The screen is working fine, I am just learning how to write tests. This is my test:
it('shows error correctly', async () => {
mockAxios.get.mockRejectedValueOnce(new Error('Async error'))
const { queryByText } = renderWithRedux(<DiscoverScreen />)
await waitFor(() => {
expect(queryByText(ErrorMessages.GeneralErrorToast)).not.toBeNull()
})
await waitForElementToBeRemoved(() => queryByText(ErrorMessages.GeneralErrorToast), { timeout: 5000 })
})
What am I not doing right? Definitely there is an issue with react native testing, because there are problems for certain async querying, especially when you have several of them. I found that here: https://github.com/callstack/react-native-testing-library/issues/379#issuecomment-720734366
I am using native base for showing the Toast, which is using Animated I think. Should I use jest.useFakeTimers() and how?
After researching how the Toast in native base works (this could be done when you open the source code in github - https://github.com/GeekyAnts/NativeBase/blob/master/src/basic/ToastContainer.js), I found that it uses Animated.timing.
So I had to find out how to deal with react native animations in tests. That article had a solution that worked for me: https://medium.com/#joncardasis/react-native-how-to-test-components-implementing-animated-with-jest-8cabb5fc2730
After I added the code in my jest setup file, this is how my test looks:
global.withAnimatedTimeTravelEnabled(async () => {
const { queryByText } = renderedComponent
await waitFor(() => {
expect(queryByText(ErrorMessages.GeneralErrorToast)).not.toBeNull()
})
global.timeTravel(Constants.ErrorToastDelay)
expect(queryByText(ErrorMessages.GeneralErrorToast)).toBeNull()
})
It works and now the test passes with no warnings!
One little adjustment in my jest configuration was also missing. I had to add this:
"testEnvironment": "jsdom"
I hope this could help someone else, too!

API response is not accessible in ComponentDidMount but in render I can use

Hi I am working on React Native app. I am using Redux and Saga. I call the API in componentDidMount.
async componentDidMount() {
let data = this.props.navigation.getParam("returnProductData");
if (data) {
console.log("Return Here");
this.props.getProductReturnAction(data)
this.setState({
returnQty:parseInt(this.props.product.item_ordered)-parseInt(this.props.product.already_return_qty)
});
console.log(this.state.returnQty,"Return quty"); //coming undefined
console.log(this.props.product, "product"); // undefined
console.log(this.props.product.item_ordered); //undefined
}
}
I have to set the state in componentDidMount for returnQty. But, state is not accessible here. It's working fine in render method. I can use all the product object. But, it is coming empty in componentDidMount. I tried using async and await but it's not working.
// Dispatch Methods
const mapDispatchToProps = dispatch => {
return bindActionCreators(
{ getProductReturnAction, submitProductReturnAction },
dispatch
);
};
// Props
const mapStateToProps = state => {
return {
product: state.myOrdersReducer.returnProduct
};
};
I can't be able to find out the bug please help to find out the best solution.
When you are making API calls through redux/saga, you can not use async await, as the frameworks will just dispatch an action and return back, the listeners which are registered for the action will be triggered and then after they complete their work they will dispatch a new action and respect reducer will handle the response.
Explained above is general scenario.
In your scenario,
You are dispatching the action returned by getProductReturnAction which will give say GET_PRODUCTS action.
A saga would be registered for GET_PRODUCTS, say getProducts, this get invoked.
This will perform the API call once the response is received it will dispatch GET_PRODUCTS_SUCCESS along with the products data.
Corresponding reducer which handles GET_PRODUCTS_SUCCESS will get called and that updates returnProduct and as you are registered for that in your component the render method gets called (as the props are changed) and hence product data is available in your render method.
This is working perfectly correct. I don't see anything wrong here.
As the data is available in props use the same u do not need to do a setState again on that.

Waiting for async before register component in react-native

I need to wait for async storage and then init app, because I store auth token here and want to show correct scene for user if he was authorised:
(async () => {
const viewer = JSON.parse(await AsyncStorage.getItem('viewer'));
// ...
const RootContainer = () => (
// ...
);
AppRegistry.registerComponent('yawaloo', () => RootContainer);
})();
I have moved to react-native 0.40.0 from 0.34.1 and now have an error "Module AppRegistry is not a registered callable".
In previous version everything were ok. How can I wait for some actions and then start render RootContainer?
One idea is to use splash screen. More specifically use a state in your RootContainer to determine whether to show a splash screen or your main UI. Set the state to false (show splash) initially then after you read the token from async storage, then set the state to true.
Part of why apps have splash screens is to deal with situation like this. HTH