state not updated inside specified function - react-native

So i'm trying to change my state like this
export default function RegisterScreen({navigation}) {
const {register, control, handleSubmit, errors} = useForm({
resolver: yupResolver(schema),
});
const [state, setState] = useState({current_state: 'initial'});
const onSubmit = (data) => {
console.log(state.current_state);
setState({current_state: 'login_process'});
console.log(state.current_state);
});
}
inside on submit. but the state is not changing it is stuck on initial
then i'm trying to do this
console.log(this.state.current_state);
this.setState({current_state: 'login_process'}, () => {
console.log(this.state.current_state);
});
but i get this error
Possible Unhandled Promise Rejection (id: 0): TypeError: undefined is
not an object (evaluating '_this.state.current_state')
how can i fix it ? and is it the proper way to do state management in react native ?

You can't do async actions with useState hook. If you want to trigger changes, try something like this:
export default function RegisterScreen({navigation}) {
const {register, control, handleSubmit, errors} = useForm({
resolver: yupResolver(schema),
});
const [state, setState] = useState({current_state: 'initial'});
useEffect(() => {
console.log(`state.current_state has been changed to: ${state.current_state}`)
}, [state.current_state])
const onSubmit = (data) => {
console.log(state.current_state);
setState({current_state: 'login_process'});
console.log(state.current_state);
});
}
The error you are receiving is that you can't use this in functional components.
You can go further with your useEffect hook and do something like this:
useEffect(() => {
if(state.current_state === 'login_process') {
// add some logic that should happen when login is processing
}
}, [state.current_state])

I think the problem is not in the code but in not putting
e.preventDefault()
try the following:
const onSubmit = (e,data) => {
e.preventDefault()
console.log(state.current_state);
setState({current_state: 'login_process'});
console.log(state.current_state);
});
}

Related

How to clean up React-Native useEffect with axios

Currently I have defined in a functional component a useEffect as below
useEffect(() => {
(async function () {
posts.current = await BlogConsumer.getBlogPosts();
setLoading(false);
})();
return () => {
BlogConsumer.call_controller.abort();
};
}, []);
where this BlogConsumer is defined as below
class BlogConsumer {
static posts = {};
static call_controller = new AbortController();
static async getBlogPosts() {
await axios
.get('https://nice.api', {
signal: this.call_controller.signal,
})
.then(response => {
// treatment for success
})
.catch(error => {
// treatment for erros
});
return this.posts;
}
}
export default BlogConsumer;
The overral ideia is that in the render of the component I'll be calling a static method from my consumer and will retrieve the necessary data. For the pourpuse of not having memory leaks, I have my callback function in my useEffect that will abort my call whenever I unmount the component, but this is not working. React's message of Warning: Can't perform a React state update on an unmounted component. still appears if I enter the component and leave the screen before the API call is finished. I don't know where I am wrong, so I'd like a little help.
Thanks in advance.
You could just cancel the request on unmount. Like this:
export const fetchData = async (signal) => {
try {
const res = await instance.get("/pages/home", {
signal,
});
return res.data;
} catch (error) {
return Promise.reject(error);
}
};
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => {
controller.abort()
};
}, []);

How to commit mutation from `plugins` directory using Nuxt.js?

I have just run into such a problem, I am trying to customize Axios module, My aim is to access my dom.js vuex module state from 'plugins' directory, The code below works but I have the following error in the console
Do not mutate vuex store state outside mutation handlers
So, The reason for this error is also clear to me, I wonder how I can Commit mutation from 'plugins' directory to my dom.js vuex module?
Thanks!
//plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.state.dom.alertIs = true
redirect('/')
}
})
}
/store/dom.js
export const state = () => ({
alertIs:false
})
Declare a mutation (named "SET_DOM_ALERT") in your store:
// store/dom.js
export default {
state: () => ({
alertIs: false
}),
mutations: {
SET_DOM_ALERT(state, value) {
state.alertIs = value
}
}
}
Then, use store.commit('dom/SET_DOM_ALERT', newValue) in your plugin (notice the dom/ prefix for the namespace):
// plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.commit('dom/SET_DOM_ALERT', true) // 👈
redirect('/')
}
})
}
demo

Reset useLazyQuery after called once

I'm using useLazyQuery to trigger a query on a button click. After the query is called once, the results (data, error, etc) are passed to the component render on each render. This is problematic for example when the user enters new text input to change what caused the error: the error message keeps reapearing. So I would like to "clear" the query (eg. when user types new data into TextInput) so the query results return to there inital state (everything undefined) and the error message goes away.
I can't find any clear way to do this in the Apollo docs, so how could I do that?
(I though of putting the query in the parent component so it does not update on each rerender, but I'd rather not do that)
This is how I have my component currently setup:
import { useLazyQuery } from 'react-apollo'
// ...
const [inputValue, setInputValue] = useState('')
const [getUserIdFromToken, { called, loading, data, error }] = useLazyQuery(deliveryTokenQuery, {
variables: {
id: inputValue.toUpperCase(),
},
})
useEffect(() => {
if (data && data.deliveryToken) {
onSuccess({
userId: data.deliveryToken.vytal_user_id,
token: inputValue,
})
}
}, [data, inputValue, onSuccess])
// this is called on button tap
const submitToken = async () => {
Keyboard.dismiss()
getUserIdFromToken()
}
// later in the render...
<TextInput
onChangeText={(val) => {
setInputValue(val)
if (called) {
// clean/reset query here? <----------------------
}
})
/>
Thanks #xadm for pointing out the solution: I had to give onCompleted and onError callbacks in useLazyQuery options, and pass the variables to the call function, not in useLazyQuery options. In the end the working code looks like this:
const [inputValue, setInputValue] = useState('')
const [codeError, setCodeError] = useState<string | undefined>()
const [getUserIdFromToken, { loading }] = useLazyQuery(deliveryTokenQuery, {
onCompleted: ({ deliveryToken }) => {
onSuccess({
userId: deliveryToken.vytal_user_id,
token: inputValue,
})
},
onError: (e) => {
if (e.graphQLErrors && e.graphQLErrors[0] === 'DELIVERY_TOKEN_NOT_FOUND') {
return setCodeError('DELIVERY_TOKEN_NOT_FOUND')
}
return setCodeError('UNKNOWN')
},
})
const submitToken = () => {
Keyboard.dismiss()
getUserIdFromToken({
variables: {
id: inputValue
},
})
}

How to test 'didFocus' from react navigation with jest

I have a component that looks like this
async componentDidMount() {
const { navigation } = this.props
this.subs = [
navigation.addListener('didFocus', () => this.onComponentFocus()),
]
}
onComponentFocus() {
const { dispatch } = this.props
dispatch(fetchDevices())
}
Now i want to write a test that chekcs fetchDevice got called once. The first idea was to mock Navigation like this
const navigation = {
navigate: jest.fn(),
}
But now how do I check this.subs and how do i check fetchDevices got fired?
If we suppose that fetchDevices comes from a library
Component.spec.js
import fetchDevices from 'device-fetcher';
jest.mock('device-fetcher');
// as your component accepts the dispatch function
// you can create it as mock function
const mockDispatch = jest.fn();
// since in your implementation you're calling navigation.addListener
const mockNavigation = {
navigate: jest.fn(),
// it should also have
addListener: jest.fn()
};
describe('Component', () => {
const wrapper = shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} />);
describe('navigation didFocus', () => {
beforeAll(() => {
// get .addEventListener calls with 'didFocus'
mockNavigation.addEventListener.mock.calls
.filter(([eventName]) => eventName === 'didFocus')
// iterate over the "attached" handlers
.map(([eventName, eventHandler]) => {
// and trigger them
eventHandler();
});
});
it('should have called the dispatch with the result of fetchDevices', () => {
expect(mockDispatch).toHaveBeenCalledWith(
fetchDevices.mock.results[0].value
);
});
});
});
note: it's not tested, just a solution outline
edit: if the fetchDevices is a property instead of mocking the library you define a mock function
const fetchDevices = jest.fn();
// and pass it to the component
shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} fetchDevices={fetchDevices} />);
and then you should have the same assertions for it

React Native/Jest TypeError: Cannot read property 'params' of undefined - testing with jest

I'm trying to create a test in an application with jest and this is some lines of my code:
import React, { Component } from 'react';
import {...} from 'react-native';
import jwt_decode from 'jwt-decode';
class CreateProduct extends Component {
constructor(props) {
super(props);
this.keyboardHeight = new Animated.Value(0);
this.imageHeight = new Animated.Value(199);
this.state = {
isButtonsHidden: false,
title: '',
price: '',
description: '',
isDialogVisible: false,
messageError: '',
};
}
_goBack = async () => {
const {state} = this.props.navigation;
var token = state.params ? state.params.token : undefined;
this.props.navigation.navigate('MyProducts', {token:token});
}
I want to test the navigation:
this.props.navigation.navigate('MyProducts', {token:token});
Now this is the attempt to test:
describe('Testing navigation', () =>{
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation:{
navigate: spyNavigate
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props}/>)
wrapper.setState({params: params})
})
it('should test navigation', () => {
wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
But I'm receiving this error.
I'm assuming that there is an error with the way I'm passing the const params. Can you help me telling what's the best way I can do this to simulate a token and that way I can navigate in the screen?
Thanks.
Rootcause is your _goBack is async. But you don't await till it ends before running expect. Even more: jest also does not wait _goBack to finish so you don't even see an error
Cannot read property 'params' of undefined
that happens because you don't mock state in navigation.params.
To work with async code there are 2 different approaches in Jest: either returning Promise from the it() or running done() callback manually(it's passed as 1st argument in it()).
I'll picking 2nd since it allows us also await until goBack is finished before running expect:
describe('Testing navigation', () => {
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation: {
navigate: spyNavigate,
state: {}
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props} />)
wrapper.setState({ params: params })
})
it('should test navigation', async () => {
await wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
Or without using async/await it would look like
it('should test navigation', () => {
return wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled());
})
that looks messy
Or using done() callback
it('should test navigation', (done) => {
wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled()).
then(done);
})