Getting error while using useHeaderHeight() in unit-testing - react-native

Using #react-navigation/stack library in my project, while doing unit testing (using jest) I am getting this error
Couldn't find the header height. Are you inside a screen in Stack?
index.spec.js
it('should have a title and subtitle', () => {
const { getByText } = render(<Otp {...props} />); });
Otp component
<SafeAreaView style={styles.mainContainer}>
<KeyboardAvoidingView
keyboardVerticalOffset={useHeaderHeight()} --> getting error at this line
>
<Text>...</Text>
</KeyboardAvoidingView>
</SafeAreaView>
Also tried a solution in which I mocked a navigator but got no success.

I came across the same issue today. What worked for me was to mock the entire module to return an object with useHederHeight. I think this is fine for unit testing and if you need to mock extra features you can do that too.
jest.mock('#react-navigation/elements', () => ({
useHeaderHeight: jest.fn().mockImplementation(() => 200)
}))

Related

React Native on Android focus doesn't open the keyboard

We are facing some very weird issue with opening keyboard on Android device.
It works great on iOS, 90% on Android devices, but on some devices it simply don't work = the keyboard don't show despite the input field has focus.
We tried it with different approaches (with refs, with timeout and without ref either).
All with the same result :/. One of the devices we are trying has API 30, so it even doesn't seem to be an old API problem.
Have anybody facing similar issue? What can cause the issue? Any tips how to debug it?
Here is code:
export const AddNotificationModal: AddNotificationModalT = ({
visible,
category,
onCloseModal,
}) => {
const input = useRef<TextInput>(null);
...
useEffect(() => {
if (visible) {
// 1.
// InteractionManager.runAfterInteractions(() => {
// if (input?.current) {
// input.current.focus();
// }
// });
// 2.
input.current?.focus();
// 3.
// setTimeout(() => input.current?.focus(), 150);
}
}, [visible]);
return (
<Modal
name={AddNotificationModal.name}
visible={visible}>
...
<View>
<TextInput autoFocus={true} ref={input} />
<Button dark onPress={() => onConfirm()}>
{t('shared:buttons.safe')}
</Button>
</View>
</Modal>
);
};

Native Base Picker generating "Unsafe legacy lifecycles"

I created a very simple page in react native. However, I'm getting the warning:
Warning: Unsafe legacy lifecycles will not be called for components using new component APIs.
%s uses %s but also contains the following legacy lifecycles:%s%s%s
The above lifecycles should be removed. Learn more about this warning here:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html, Styled(PickerNB), getDerivedStateFromProps(), ,
UNSAFE_componentWillReceiveProps,
It is happening because the native-base Picker. If I remove the picker, I do not receive the warning.
...
class ChangeProperty extends Component {
constructor(props) {
super(props);
this.state = {
selectedProperty: '1'
};
}
componentDidMount() {
this.props.getProperties(); // It just loads a properties data from action component
}
onChangeProperty(value) {
this.setState({
selectedProperty: value
});
}
updatePropertyBTN = async () => {
await AsyncStorage.setItem('CurrentPropertyID', this.state.selectedProperty);
NavigationService.navigate('iRent');
}
...
<Picker
mode="dropdown"
iosHeader="Select Property"
placeholder="Property"
iosIcon={<Icon name="arrow-down" />}
selectedValue={this.state.selectedProperty}
textStyle={{ color: '#C0C0C0' }}
style={{ width: '100%' }}
onValueChange={(text) => this.onChangeProperty(text)}
>
{Object.keys(this.props.properties).map((key) => {
return (
<Picker.Item
label={this.props.properties[key]}
value={key}
key={key}
/>
);
})}
</Picker>
}
It is not causing any error in my code, but the warning message in the terminal is disturbing me because I do not know what is causing it.
Thanks
The warning is occurring because the NativeBase picker appears to be using legacy life cycle methods (eg componentWillReceiveProps like was mentioned in the warning) that are no longer supported by React - this has nothing to do with your code.
Ensure your NativeBase is updated to the latest package version and if it is you can raise an issue on their repo here

How can I check value of the refreshControl prop in jest for my react-native app?

I am trying to do a jest unit test which simply confirms that the refreshControl prop is active in my ScrollView.
I currently have a component that essentially renders the following
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh}
/>
}
{children}
/>
It works fine in practice, and in my test, I am checking to confirm that the prop is as expected.
import { RefreshControl } from 'react-native'
const layout = shallow(<Element />)
const refreshControl = <RefreshControl
onRefresh={jest.fn()}
refreshing={false}
/>
expect(layout.prop('refreshControl')).toEqual(refreshControl)
I then get this error when running my test:
Expected value to equal:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
Received:
<RefreshControlMock onRefresh={[Function mockConstructor]} refreshing={false} />
I assume it's because the instance of the onRefresh function differs, but not sure how to control that. Does anyone perhaps know?
Please find below steps to call Pull to Refresh to cover coverage.
First, get access to the scrollView component through testID
Fetch refresh control present in the scroll view component
call onRefresh from test case
I have added the below Example for reference.
test('refresh control', async () => {
const props = createTestProps();
const component = render(
<Provider store={store}>
<ScrollViewComponent {...props} />
</Provider>
);
const scrollView = component.getByTestId('refreshControl');
expect(scrollView).toBeDefined();
const { refreshControl } = scrollView.props;
await act(async () => {
refreshControl.props.onRefresh();
});
});
Just realised that I need to check that the function is the same one I pass into the component, in the test, so basically, the function in the component that matches.
this._onRefresh
MW UMESH's answer helped me quite a lot (and I therefore upvoted it) but since you get the refreshControl prop synchronously, you can remove the async test here.
So replace
await act(async () => {
refreshControl.props.onRefresh();
});
With
refreshControl.props.onRefresh();
And the reload mechanism will trigger.

Getting undefined from `this.refs[var-name].value`

I am using pusher for realtime updates. On client side there is multiple media is loaded from database. and adding ref to <Text> component and setting it's value to the current mediaID.
this.state.feed.map((media) => {
<View>
<Text ref={media._id}>{media.likes}</Text>
<Text ref={media._id}>{media.name}</Text>
</View>
})
I am accessing the value of ref in componentDidMount when binding the pusher trigger event.
async componentDidMount() {
channel.bind('updateLikes', (data) => {
alert(this.refs[data.mediaId].value)
})
}
But, it's giving the undefined instead of the value of the <Text> component. I tried the solution from other posts, but i cound't find any of them working for my code.

Opening context menu on long press in React Native

I'd like to have a context menu triggered on long press different places using React Native.
I.e. in a dialer like the default dailer. You can long-click on any contact and get a 'copy number' menu. And also you can long-click on the name of the person once you've opened their 'contact card'.
The straight-forward way needs a lot of copy-pasted boilerplate, both components and handlers.
Is there a better pattern for doing this?
All Touchable components (TouchableWithoutFeedback, TouchableOpacity etc.) has a property called onLongPress. You can use this prop to listen for long presses and then show the context menu.
To eliminate code mess and doing lots of copy paste you can separate your context menu as a different component and call it when the long press happen. You can also use an ActionSheet library to show the desired options. React native has a native API for iOS called ActionSheetIOS. If you get a little bit more experience in react and react-native you can create a better logic for this but I'm going to try to give you an example below.
// file/that/contains/globally/used/functions.js
const openContextMenu = (event, user, callback) => {
ActionSheetIOS.showActionSheetWithOptions({
options: ['Copy Username', 'Call User', 'Add to favorites', 'Cancel'],
cancelButtonIndex: [3],
title: 'Hey',
message : 'What do you want to do now?'
}, (buttonIndexThatSelected) => {
// Do something with result
if(callback && typeof callback === 'function') callback();
});
};
export openContextMenu;
import { openContextMenu } from './file/that/contains/globally/used/functions';
export default class UserCard extends React.Component {
render() {
const { userObject } = this.props;
return(
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done')}>
<TouchableWithoutFeedback onLongPress={(event) => openContextMenu(event, userObject, () => console.log('Done'))}>
<Text>{userObject.name}</Text>
<Image source={{uri: userObject.profilePic }} />
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
);
}
}
Similarly as the previous answer combine onLongPress with imperative control for popup menu - something like
<TouchableWithoutFeedback onLongPress={()=>this.menu.open()}>
<View style={styles.card}>
<Text>My first contact name</Text>
<Menu ref={c => (this.menu = c)}>
<MenuTrigger text="..." />
<MenuOptions>
// ...
</MenuOptions>
</Menu>
</View>
</TouchableWithoutFeedback>
When it comes to a lot of boilerplate - in React you can do your own components that you can reuse everywhere thus reducing boilerplate (and copy&paste)
See full example on https://snack.expo.io/rJ5LBM-TZ