How to pass the value of useState to BackHandler.addEventListener - react-native

I'm using React Hooks and when I create an event listener for android back press handler, the state inside the callback function handler is empty!
In class components it works fine!
'use strict';
import React, { useState, useEffect } from 'react';
import { BackHandler } from 'react-native';
import TextInput from '../../../../components/TextInput';
export default function Foo() {
const [comment, setComment] = useState('');
useEffect(() => {
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose
);
return () => handler.remove();
}, []);
const handleValidateClose = () => {
/* Here is empty */
console.log(comment);
};
return <TextInput onChangeText={setComment} value={comment} />;
}
The value should be the useState changed

handleValidateClose should be on your dependency array.
You can use your function outside the useEffect but should use with useCallback.
const handleValidateClose = useCallback(() => {
console.log(comment);
return true;
}, [comment]);
useEffect(() => {
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose,
);
return () => handler.remove();
}, [handleValidateClose]);
You can also move the definition to inside useEffect, and add a comment as a dependency.
useEffect(() => {
const handleValidateClose = () => {
console.log(comment);
return true;
};
const handler = BackHandler.addEventListener(
'hardwareBackPress',
handleValidateClose,
);
return () => handler.remove();
}, [comment]);
To clean things up, create a useBackHandler.
export default function useBackHandler(handler) {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handler);
return () => {
BackHandler.removeEventListener('hardwareBackPress', handler);
};
});
}
And use it like this:
const handleValidateClose = () => {
console.log(comment);
return true;
};
useBackHandler(handleValidateClose);
Please config your project to use the eslint-plugin-react-hooks. That's a common pitfalls that the plugin would help you with.

Related

useEffect not triggered

I'm trying to use redux with useEffect to update/get the redux state but useEffect is totally not running at all but I have no idea what is going on. I can't even get the "hi"
import { useSelector, useDispatch } from 'react-redux';
import { setDisplayLogsheet, getLogsheets } from '../redux/actions';
...
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log("hi")
dispatch(getLogsheets());
dispatch(setDisplayLogsheet(logsheets));
}, []);
Any help please? Thanks
UPDATE: here's more code to understand
App.js:
I have added the store inside the provider
const Stack = createStackNavigator();
export default function App() {
return(
<Provider store={Store}>
<NavigationContainer>
...
<Provider />
}
home.js:
tried to useSelector to get the logsheets and displayLogsheets and useEffect to dispatch, but the the useEffect is totally not running
export default function Home({navigation}) {
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){
dispatch(setDisplayLogsheet(logsheets))
}
}, [dispatch, logsheets])
console.log(logsheets)
console.log(displayLogsheet)
return (
<>
<SafeAreaView>
<ScrollView>
<HomeTopStack logsheet={displayLogsheets} iterateDocket={iterateDocket} />
<ScanBarcodeButton navigation={navigation} />
{displayLogsheets.data.DO.map(logsheet => (
<TouchableOpacity onPress={() => navigation.navigate('Details', logsheet)}>
<DOCards logsheet={displayLogsheets} />
</TouchableOpacity>
))}
</ScrollView>
</SafeAreaView>
</>
)
}
store.js:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logsheetReducer from './reducers';
const rootReducer = combineReducers({ logsheetReducer });
export const Store = createStore(rootReducer, applyMiddleware(thunk));
reducer.js:
this is the reducer to set display logsheet and also to get the dummy logsheet data
import { SET_DISPLAY_LOGSHEET, GET_LOGSHEETS } from "./actions";
const initialState = {
logsheets: {},
displayLogsheet: {},
}
function logsheetReducer(state = initialState, action) {
switch (action.type) {
case SET_DISPLAY_LOGSHEET:
console.log("inside logsheetReducer, SET_DISPLAY_LOGSHEET")
return { ...state, displayLogsheet: action.payload };
case GET_LOGSHEETS:
console.log("inside logsheetReducer, GET_LOGSHEET")
return { ...state, logsheets: action.payload };
default:
return state;
}
}
export default logsheetReducer;
actions.js:
import CreateFakeLogsheets from "../data/logsheet";
export const SET_DISPLAY_LOGSHEET = 'SET_DISPLAY_LOGSHEET';
export const GET_LOGSHEETS = 'GET_LOGSHEETS';
const logsheets = CreateFakeLogsheets(2,3)
export const getLogsheets = () => {
console.log("inside getLogsheets")
try {
return dispatch => {
dispatch({
type: GET_LOGSHEETS,
payload: logsheets
})
}
} catch (error) {
console.log(error)
}
}
export const setDisplayLogsheet = displayLogsheet => {
console.log("inside setDisplayLogsheets")
return dispatch => {
dispatch({
type: SET_DISPLAY_LOGSHEET,
payload: displayLogsheet
});
}
};
here's most of the code with redux and also the useEffect. Any help please
Without knowing how the rest of the code is structured, I would split the effect in two, like this:
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){ // only dispatch this if logsheets have been fetched
dispatch(setDisplayLogsheets(logsheets))
}
}, [dispatch, logsheets])

useEffect returns unhandled promise

I have been for several hours trying to get an API to be called in ReactNative useEffect hook. Sometimes when I restart my app the value is resolved. But most of the time, I have an Unhandled promise rejection. I googled and tried various methods. I tried using .then etc.. I just can't figure it out.
import React, { useState, useContext, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
import { EvilIcons } from '#expo/vector-icons';
import jsonServer from '../api/jsonServer';
const ShowScreen = ({ navigation }) => {
const id = navigation.getParam('id');
const [post, setPost] = useState([]);
const getBlog = async () => {
const result = await jsonServer.get(`http://0.0.0.0/blog/docroot/jsonapi/node/article/${id}`);
return result;
}
useEffect(() => {
async function setToState() {
const val = await getBlog();
setPost(val);
}
setToState();
},[]);
return (
<View>
<Text>Here { console.log(post) }</Text>
</View>
);
};
ShowScreen.navigationOptions = ({ navigation }) => {
return {
headerRight: (
<TouchableOpacity
onPress={() =>
navigation.navigate('Edit', { id: navigation.getParam('id')
})}
>
<EvilIcons name="pencil" size={35} />
</TouchableOpacity>
)
};
};
const styles = StyleSheet.create({});
export default ShowScreen;
What you could do is something like this:
....
....
const [post, setPost] = useState([]);
const [isMounted, setIsMounted] = useState(false);
const getBlog = async () => {
const result = await jsonServer.get(`http://0.0.0.0/blog/docroot/jsonapi/node/article/${id}`);
return result;
}
useEffect(() => {
setIsMounted(true)
async function setToState() {
// using try catch I'm handling any type of rejection from promises. All errors will move to catch block.
try{
const val = await getBlog();
// checking if component is still mounted. If mounted then setting a value. We shouldn't update state on an unmounted component.
if(isMounted){
setPost(val);
}
} catch(err){
console.log("Error", err)
}
}
setToState();
return () => {
// Setting is mounted to false as the component is unmounted.
setIsMounted(false)
}
},[]);
I believe this will solve your Unhandled promise rejection error. Please try if it still doesn't solve the issue will create the same in Sanck.
I think my issue was not just promise, the issue is also seems to be me not handling undefined/null in the state. The below code is working for me.
import React, { useState, useContext, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
import { EvilIcons } from '#expo/vector-icons';
import jsonServer from '../api/jsonServer';
const ShowScreen = ({ navigation }) => {
const id = navigation.getParam('id');
const [post, setPost] = useState([]);
const getBlog = async () => {
const result = await jsonServer.get(`http://hello.com/jsonapi/node/article/${id}`).then(
res => {
setPost(res)
return res;
}, err => {
console.log(err);
});
}
useEffect(() => {
setPost(getBlog());
},[]);
return (
<View>
<Text>{ post.data ? post.data.data.id : "" }</Text>
</View>
);
};
export default ShowScreen;
Note: I am setting the state in useEffect as well as in the request. I am yet to check if I can just do it once.

React native: useSelector redux is empty

I am new and i want to using react native to create android application so after creating project i installed redux and redux thunk and do every config that redux wants to work .
I create a action file :
export const GETSURVEYOR = 'GETSURVEYOR';
const URL = "http://192.168.1.6:3000/";
export const fetchSurveyor = () => {
return async dispatch => {
const controller = new AbortController();
const timeout = setTimeout(
() => { controller.abort(); },
10000,
);
const response = await fetch(`${URL}GetSurveyorList`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}),
signal: controller.signal
});
clearTimeout(timeout);
const resData = await response.json();
dispatch({
type: GETSURVEYOR,
surveyorList: resData.SurveyorList
});
}
}
after that i create reducer to handle this data :
import {GETSURVEYOR} from '../actions/surveyor'
const initialState = {
surveyorList: []
}
export default (state = initialState, action) => {
switch (action.type) {
case GETSURVEYOR:
return {
...state,
surveyorList: action.surveyorList
};
Now i am using by useSelector, useDispatch from 'react-redux .
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
await dispatch(surveyorActions.fetchSurveyor());
console.log('run use Callback');
console.log('returned :', survayers );
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, [dispatch]);
return [loadSurvayer, surveyorCount];
}
When for first time this paged is rendered , of course that survayers is empty but after fetch data in action and set state to reducer , survayers nut to be an empty.
But i get empty still ? I am sure data is fetched from services but i got empty from survayers ?
LOG Running "RNAuditMngm" with {"rootTag":1}
LOG run use Callback
LOG returned : []
LOG run use Callback
LOG returned : []
if i change my useEffect code to this:
useEffect(() => {
loadSurvayer();
}, [dispatch,survayers]);
I fall to loop !!!! How could i change code without loop?
I think everything works fine, but you're not using the console.log in the right place. When you run the loadSurvayer the survayers is empty. It is empty even the second time because you are not passing it as a dependency in the useEffect hook. And like you said, if you pass it as a dependency, then it causes an infinite loop, and that's right because whenever the survayers change, that function will be called again and so on.
So, here's what you have to do:
Remove the dispatch dependency from your useEffect hook.
Change the console.log's outside of the loadSurvayer function.
Remove the await from the dispatch call because it is synchronous.
Here's how to modify your code to work the right way:
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
return [loadSurvayer, surveyorCount];
}
Improvement bonus and suggestion: remove the surveyorCount state variable because you don't actually need it as you can return the count directly.
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import * as surveyorActions from '../store/actions/surveyor';
export default () => {
// Remove the `surveyorCount`
//const [surveyorCount, setSurveyorCount] = useState(0);
const survayers = useSelector(state => state.surveyor.surveyorList);
const dispatch = useDispatch();
const loadSurvayer = useCallback(async () => {
dispatch(surveyorActions.fetchSurveyor()); // Remove the `await`
console.log('run use Callback');
// setSurveyorCount(survayers.length);
}, [dispatch]);
useEffect(() => {
loadSurvayer();
}, []); // <-- remove the `dispatch` from here.
console.log('returned :', survayers ); // <-- Move the console log here
//return [loadSurvayer, surveyorCount];
return [loadSurvayer, survayers.length]; // <-- Use `survayers.length` instead of `surveyorCount`
}
In useSelector shouldn't you read surveyerList like this state.surveyorList ?. your state doesn't have any object named surveyor but you are currently reading like state.surveyor.surveyorList

Can't I return a form custom error anymore with "Altering the Form Values before Submitting"?

React-admin version 2 used redux-form. Together with the documentation "Altering the Form Values before Submitting" i created a custom button that returns a form error from a custom function, or does a save.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { crudCreate, SaveButton, Toolbar } from 'react-admin';
import { SubmissionError } from 'redux-form';
const myValidate = (values) => {
const errors = {};
if (values.files < 1) errors.files = 'resources.Order.errors.files';
return errors;
};
// A custom action creator which modifies the values before calling the default crudCreate action creator
const saveWithNote = (values, basePath, redirectTo) =>
crudCreate('posts', { ...values, average_note: 10 }, basePath, redirectTo);
class SaveWithNoteButtonView extends Component {
handleClick = () => {
const { basePath, handleSubmit, redirect, saveWithNote } = this.props;
return handleSubmit(values => {
return new Promise((resolve, reject) => {
const errors = myValidate(values);
if (errors) {
reject(new SubmissionError(errors))
} else {
resolve( saveWithNote(values, basePath, redirect) );
};
}
});
});
};
render() {
const { handleSubmitWithRedirect, saveWithNote, ...props } = this.props;
return (
<SaveButton
handleSubmitWithRedirect={this.handleClick}
{...props}
/>
);
}
}
const SaveWithNoteButton = connect(
undefined,
{ saveWithNote }
)(SaveWithNoteButtonView);
Now in React-admin version 3 "Altering the Form Values before Submitting" Can't I do this anymore?
This code does not work:(
import React, { useCallback } from 'react';
import ProcessIcon from '#material-ui/icons/HeadsetMic';
import { useForm, useFormState } from 'react-final-form';
import { SaveButton } from 'react-admin';
const validateProcess = (values) => {
const errors = {};
if (values.files < 1) errors.files = ['resources.Order.errors.files'];
return errors
};
const SaveWithProcessButton = ({ handleSubmitWithRedirect, ...props }) => {
const form = useForm();
const formState = useFormState();
const handleClick = useCallback(() => {
return new Promise((resolve, reject) => {
const errors = validateProcess(formState.values);
if (errors) {
reject(errors)
} else {
form.change('status', "DRAFT");
resolve( handleSubmitWithRedirect('show') );
};
});
}, [form]);
return <SaveButton {...props} handleSubmitWithRedirect={handleClick} label="ra.action.process" icon={<ProcessIcon />} />;
};
export default SaveWithProcessButton;
I manage to do this. Try my code below, I hope it will works for also for you.
const handleClick = useCallback(() => {
if (!formState.valid) {
form.submit();
return;
} else { ... your code goes here }
}, [form]);

How to detect when keyboard is opened or closed in React Native

How to detect if user close the keyboard in react native, I want to call a function when user closed the keyboard.
and if you can answer to detect keyboard is open too it will be appreciated, thanks.
I'm on the react native latest version 0.56
Thank you guys for your answers. Here is the hooks version if someone is interested:
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
setKeyboardVisible(true); // or some other action
}
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardVisible(false); // or some other action
}
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
1. You can use Keyboard class from facebook.
Here is a sample code.
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
componentWillMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow () {
alert('Keyboard Shown');
}
_keyboardDidHide () {
alert('Keyboard Hidden');
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}
###2. You can use some other npm dependency also, like react-native-keyboard-listener.
Import the component into the file you want to use it:
import KeyboardListener from 'react-native-keyboard-listener';
Use the component directly in your code. The component won't render anything
<View>
<KeyboardListener
onWillShow={() => { this.setState({ keyboardOpen: true }); }}
onWillHide={() => { this.setState({ keyboardOpen: false }); }}
/>
</View>
To install this dependency run below command.
npm install --save react-native-keyboard-listener
Choose any you feel more convenient.
I wrapped this up in a hook:
import { useState, useEffect } from 'react';
import { Keyboard } from 'react-native';
export const useKeyboardVisible = () => {
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
setKeyboardVisible(true);
},
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardVisible(false);
},
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
return isKeyboardVisible;
};
The hook returns a boolean flag that can be used to apply logic conditionally or run any other effect needed.
Improved version of #Khemraj 's answer (which worked great for me) with bound methods to the instance in order to be able to update the component's state from the listener and re-render.
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
state = {
keyboardState: 'closed'
}
componentWillMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow = () => {
this.setState({
keyboardState: 'opened'
});
}
_keyboardDidHide = () => {
this.setState({
keyboardState: 'closed'
});
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}
I came across the usekeyboard hook found in #react-native-community/hooks
E.g.
import { useKeyboard } from '#react-native-community/hooks'
const keyboard = useKeyboard()
console.log('keyboard isKeyboardShow: ', keyboard.keyboardShown)
console.log('keyboard keyboardHeight: ', keyboard.keyboardHeight)
Source: https://github.com/react-native-community/hooks/blob/master/src/useKeyboard.ts
I used a small remix to the great answer from #halbano that may be useful to consider depending on your situation
export default function useKeyboardOffsetHeight(): number {
const [keyBoardOffsetHeight, setKeyboardOffsetHeight] = useState(0);
useEffect(() => {
const keyboardWillShowListener = Keyboard.addListener(
"keyboardWillShow",
(e) => {
setKeyboardOffsetHeight(e.endCoordinates.height);
}
);
const keyboardWillHideListener = Keyboard.addListener(
"keyboardWillHide",
() => {
setKeyboardOffsetHeight(0);
}
);
return () => {
keyboardWillHideListener.remove();
keyboardWillShowListener.remove();
};
}, []);
return keyBoardOffsetHeight;
}
keyBoardOffsetHeight vs isKeyboardVisible
I used integer keyBoardOffsetHeight instead of boolean isKeyboardVisible . Since it's often useful to move some of your other components based on the keyboard size. The offset is either 0 (hidden) or Y > 0 pixels (it is shown). Then I can use that in styling like so:
const keyBoardOffsetHeight = useKeyboardOffsetHeight();
...
<View
style={{
bottom: keyBoardOffsetHeight,
}}
>
keyboardWillShow vs keyboardDidShow
Secondly, I use keyboardWillShow events so that I can anticipate the keyboard appearing and fire my UI changes earlier.
The keyboardDidShow event fires once the keyboard has already appeared.
I also used keyboardWillHide instead of keyboardDidHide based on the same reasoning.
MobX version:
import { observable } from 'mobx'
import { EmitterSubscription, Keyboard } from 'react-native'
class KeyboardStore {
#observable isKeyboardVisible = false
keyboardSubs: EmitterSubscription[] = []
subKeyboard() {
this.keyboardSubs = [
Keyboard.addListener('keyboardDidShow', () => this.isKeyboardVisible = true),
Keyboard.addListener('keyboardDidHide', () => this.isKeyboardVisible = false),
]
}
unsubKeyboard() {
this.keyboardSubs.forEach(sub => sub.remove())
this.keyboardSubs = []
}
}
and inside top level App component
useEffect(() => {
store.subKeyboard()
return () => {
store.unsubKeyboard()
}
}, [])
and check anywhere in your app with store.isKeyboardVisible.
All the thing already avalable in react-native Keyboard class
import React, { Component } from 'react';
import { Keyboard, TextInput } from 'react-native';
class Example extends Component {
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
this.keyboardWillShowListener = Keyboard.addListener('keyboardWillShow', this._keyboardWillShow);
this.keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', this._keyboardWillHide);
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
this.keyboardWillShowListener.remove();
this.keyboardWillHideListener.remove();
}
_keyboardWillShow() {
console.log('Keyboard Showning')
}
_keyboardWillHide() {
console.log('Keyboard Heding')
}
_keyboardDidShow() {
alert('Keyboard Shown');
}
_keyboardDidHide() {
alert('Keyboard Hidden');
}
render() {
return (
<TextInput
onSubmitEditing={Keyboard.dismiss}
/>
);
}
}