ScrollView Getting error undefined is not a function, js engine: hermes - reactjs

I Tried to using useRef to automate scroll if I press button
The idea is scroll with my scrollview and then I can press button to scroll to another set of view in my scrollview
this is my complete slider code
import { height } from 'deprecated-react-native-prop-types/DeprecatedImagePropType'
import { onChange } from 'deprecated-react-native-prop-types/DeprecatedTextInputPropTypes'
import React , {useState,useRef} from 'react'
import { StyleSheet, View , Text , ScrollView , Pressable , Image, Dimensions , TouchableOpacity} from 'react-native'
const WIDTH = Dimensions.get('window').width
const HEIGHT = Dimensions.get('window').height
const ImageSlider = ({slides}) => {
const refScrollview = useRef()
const [currentIndex,setCurrentIndex] = useState(0)
// console.log(slides[currentIndex].banner)
const [imageActive,setImageActive] = useState(1)
const onChange = (nativeEvent) =>{
if(nativeEvent){
const slide = Math.ceil(nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width)
if(slide != imageActive){
console.log(slide)
setImageActive(slide)
console.log(refScrollview.current)
}
}
}
return(
<View style={{flex:1}}>
<ScrollView
ref={refScrollview}
// onContentSizeChange={(contentWidth, contentHeight)=> {refScrollview.current.scrollToEnd({animated: true})}}
onScroll={({nativeEvent}) => onChange(nativeEvent)}
showsHorizontalScrollIndicator={false}
pagingEnabled
horizontal
style={styles.wrap}
>
{
slides.map((e,index)=>
// <Text>{e.banner}</Text>
<View style={{flex:1}} key={index}>
<Image source={e.banner}
style={[styles.wrap]}
>
</Image>
<Image source={e.bannerFace}
style={styles.bannerFace}
>
</Image>
</View>
)
}
</ScrollView>
<View style={styles.wrapDot}>
{
slides.map((e,index)=>
<TouchableOpacity key={index} style={imageActive == index ? styles.lineActive : styles.lineOnly} onPress={() => {
refScrollview.current.scrollTo()}}>
</TouchableOpacity>
)
}
</View>
</View>
)
}
let styles = StyleSheet.create({
backgroundImage: {
flex:1,
width: null,
height: null,
// width: 400,
// height: 400,
// resizeMode: 'cover',
},
bannerFace:{
position:'absolute',
bottom:0,
right:0,
flex: 1,
width: 150,
height: 150,
resizeMode: 'contain'
},
wrap:{
width:WIDTH,
height:'100%'
},
wrapDot:{
position:'absolute',
bottom:20,
left:50,
flexDirection:'row',
alignSelf:'center'
},
lineOnly:{
padding:5,
width:70,
borderRadius:15,
borderColor:'#CECECE',
backgroundColor:'#CECECE',
marginHorizontal:5
},
lineActive:{
padding:5,
width:70,
borderRadius:15,
borderColor:'#FFF',
backgroundColor:'#FFF',
marginHorizontal:5
}
})
export default ImageSlider
I get the error TypeError: undefined is not a function, js engine: hermes
whenever i try to push/use this
onPress={() => {
refScrollview.current.scrollTo()}}
It looks like there is no scrollTo() function , am I doing this wrong ? or is it my react node.modules ?

Related

How to use Scrollview with KeyboardAvoidingView

I am a new to react native and i wonder how to use scrollView with keyboardAvoid in the following scene.I have searched for some relevant information, but still have no clue.
Following picture may be more intuitive then explain.
enter image description here
import React,{useRef,useEffect,useState} from 'react';
import { Text, View, StyleSheet,TextInput,ScrollView,KeyboardAvoidingView,TouchableOpacity } from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<View style={styles.container}>
<KeyboardAvoidingView style={{flex:1}}>
<ScrollView style={styles.scroll} >
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#24292e',
},
scroll: {
flex: 1,
paddingHorizontal: 20,
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff'
}
});
Simplified Demo online:
https://snack.expo.dev/#bravo20203/demo-about-scrollview
Thanks.
You can make this work by changing the flex: 1 to different components and by adding
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{flex: 1}}
to the KeyboardAvoidingView. I have also wrapped your content into a SafeAreaView so it does not go into the header on iOS.
Since you are dynamically expanding the TextInput on press, which changes its height, I have added a keyboardVerticalOffset to the KeyboardAvoidingView and made the overflow visible.
The final code is as follows.
import React, { useEffect, createRef, useState, useRef } from 'react';
import { useHeaderHeight } from "#react-navigation/elements";
import {
Text,
View,
KeyboardAvoidingView,
ScrollView,
TextInput,
Platform,
TouchableOpacity,
StyleSheet,
SafeAreaView
} from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={Platform.OS === 'ios' && {flex: 1}}
keyboardVerticalOffset={30}
>
<ScrollView style={styles.scroll}>
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#24292e',
flex: 1
},
scroll: {
paddingHorizontal: 20,
overflow: "visible",
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff',
}
});
I have updated your snack here. Notice that I have tested this on iOS only.
If the keyboard is opened on select, the selected Input is now fully visible.

API image not being displayed in list

I know there are several questions with this issue but mine is different.
I trying to display an image in my Flatlist card that is coming from an API. However it is not showing up.
BUT...when I display this image in another part of my code (in an Autocomplete list) using the same code basically, it works. Also, when I try an url from an image on the Web, it displays inside the flatlist correctly
Here's my Flatlist code:
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
<Card>
<CardItem>
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
Here is my Autocomplete code in which I use the same image bracket-thingy
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
{item.name}
</Text>
</TouchableOpacity>
</View>
I ran console.item(item.background_image) both inside the Flatlist(first snippet) and the Autocomplete list (Second snippet). The first shows 'undefined' and the second it shows all the URIs
App.js full code:
/*This is an example of AutoComplete Input/ AutoSuggestion Input*/
import React, { Component } from 'react';
//import react in our code.
import { StyleSheet, Text, TouchableOpacity, View, Image, FlatList, Alert, TouchableWithoutFeedback, Keyboard } from 'react-native';
//import all the components we are going to use.
import Autocomplete from 'react-native-autocomplete-input';
import { Button, List, Container, ListItem, Card, CardItem, Header, Item } from 'native-base';
import { Entypo } from '#expo/vector-icons'
//import Autocomplete component
//const API = 'https://api.rawg.io/api/games?page=1';
//Demo base API to get the data for the Autocomplete suggestion
class App extends Component {
constructor(props) {
super(props);
//Initialization of state
//films will contain the array of suggestion
//query will have the input from the autocomplete input
this.state = {
myGamesArray: [],
games: [],
query: ' ',
};
}
componentDidMount() {
//First method to be called after components mount
//fetch the data from the server for the suggestion
fetch('https://api.rawg.io/api/games?page=1&platforms=18', {
"method": "GET",
"headers": {
"x-rapidapi-host": "rawg-video-games-database.p.rapidapi.com",
"x-rapidapi-key": "495a18eab9msh50938d62f12fc40p1a3b83jsnac8ffeb4469f"
}
})
.then(res => res.json())
.then(json => {
const { results: games } = json;
this.setState({ games });
//setting the data in the games state
});
}
findGame(query) {
let i;
//method called everytime when we change the value of the input
if (query === '') {
//if the query is null then return blank
return [];
}
const { games } = this.state;
//making a case insensitive regular expression to get similar value from the film json
const regex = new RegExp(`${query.trim()}`, 'i');
//return the filtered game array according the query from the input
return games.filter(game => game.name.search(regex) >= 0);
}
AddItemsToArray = () => {
var i
//verifica se input esta vazio
if (this.state.query === '') {
return Alert.alert('Voce não selecionou um jogo')
}
//VERIFY IF GAME IS IN THE ARRAY
for (i = 0; i < this.state.games.length - 1; i++) {
if (this.state.query !== this.state.games[i].name) {
if (i === this.state.games.length - 2) {
return Alert.alert('Este jogo nao existe')
}
else {
continue
}
} else {
break
}
}
//verifica repetido
if (this.state.myGamesArray.includes(this.state.query)) {
return Alert.alert('Este jogo já foi adicionado')
}
else {
//Adding Items To Array.
this.setState(prevState => {
const { myGamesArray, query } = prevState;
return {
myGamesArray: [...myGamesArray, query.toString()],
};
},
// Use setState callback to alert with the updated state
);
}
}
render() {
const { query } = this.state;
const games = this.findGame(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss()
}}
>
<View style={styles.container}>
<View style={styles.listContainer}
>
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
console.log(item.background_image),
<Card style={{flexDirection:'row',paddingEnd:100}}>
<CardItem cardBody>
<View>
<Image
style={styles.listImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
<Text style={styles.usergameText}>
Playstation 4
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
</View>
<View>
<Header span
style={styles.header}>
<Autocomplete
inputContainerStyle={{borderColor:'transparent'}}
style={styles.autocompleteInput}
autoCapitalize="none"
autoCorrect={false}
//data to show in suggestion
data={games.length === 1 && comp(query, games[0].name) ? [] : games}
//default value if you want to set something in input
defaultValue={query}
/*onchange of the text changing the state of the query which will trigger
the findGame method to show the suggestions*/
onChangeText={text => this.setState({ query: text })}
placeholder=" Adicione os jogos que você tem"
//This below is the 'list' of autocomplete options
renderItem={({ item }) => (
//you can change the view you want to show in suggestion from here
//I GET ERROR WHEN TRYING TO ERASE (PS4) IN TEXT BOX ***NEED TO CHECK THIS
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
`${item.name}`
</Text>
</TouchableOpacity>
</View>
)}
/>
</Header>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={() => this.AddItemsToArray()}
>
<Entypo name="plus" size={50} color="#fff" />
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
flex: 1,
},
autocompleteInput: {
borderWidth: 1,
backgroundColor: "#fff",
borderColor: '#7843FF',
height: 50,
marginTop: 70,
borderRadius:10,
},
autocompleteList: {
flex:1,
flexDirection: 'row',
borderWidth:0.5,
borderColor: '#7843FF',
paddingVertical: 5,
paddingRight: 60,
},
listContainer: {
flex: 1,
position: 'absolute',
left: 10,
right: 10,
top:150,
flexDirection:'column',
justifyContent: 'center',
borderColor: '#7843FF',
},
gameText: {
fontSize: 15,
marginLeft: 10,
marginRight:30,
marginVertical:10,
color: '#000',
textAlign: 'left',
justifyContent: 'center'
},
usergameText: {
fontSize:15,
textAlign: 'left',
alignSelf:'stretch',
color: '#000',
},
gameImage: {
flex: 3,
height: 60,
width: 60,
marginLeft:10,
borderRadius: 100,
},
listImage: {
flex: 3,
height: 110,
width: 90,
marginLeft:0,
},
addButton: {
height:50,
width: 50,
position: 'absolute',
left: 371,
top: 71,
backgroundColor: '#7843FF',
borderTopRightRadius: 10,
borderBottomRightRadius:10,
},
usergameImage: {
height: 100,
width: 100,
borderRadius: 100,
},
header: {
backgroundColor:'#67E6DC'
}
});
export default App;

React Native transition maxHeight

How can I make a transition with maxHeight in React Native?
The equivalent code in React would be
function App() {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="App">
<div className={`collapsible ${isOpen ? 'opened' : ''}`}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Close' : 'Open'}
</button>
</div>
);
}
And the css
.collapsible {
max-height: 0;
transition: max-height 0.35s ease-out;
overflow: hidden;
}
.opened {
max-height: 200px;
}
Here is a working codesandbox
How can I make the same but in React Native?
My guess is that you want to persist the animation when "opening" the view.
This is not supported out of the box using a StyleSheet object in React Native. You will have to implement this yourself using the Animated API.
https://facebook.github.io/react-native/docs/animations
Example:
import React from "react"
import { Animated } from "react-native"
import { Text, StyleSheet, TextProps } from "react-native"
import { TouchableOpacity } from "react-native-gesture-handler"
class AnimatedComponent {
constructor(props){
super(props)
this.state = { open: false }
this.animatedHeightValue = new Animated.Value(0)
}
_triggerAnimation = () => {
Animated.timing(this.animatedHeightValue, {
toValue: this.state.open ? 0 : 200,
duration: 200
}).start(() => {
this.setState({open: !this.state.open})
})
}
render() {
return(
<View>
<Animated.View style={{height: this.animatedHeightValue, backgroundColor: "blue"}}>
<Text>
{"Hello world"}
</Text>
</Animated.View>
<TouchableOpacity onPress={this._triggerAnimation}>
<Text>
{"Press for magic"}
</Text>
</TouchableOpacity>
</View>
)
}
Indentation is awful sorry, but should give an idea.
Hmmm.. I'm not completely sure did you actually meant this. But in case we're on the same page, check my Snack at: https://snack.expo.dev/#zvona/maxheight-animation
Here is the actual code (in case link of the snack gets deprecated):
import React, { useState, useRef } from 'react';
import {
Animated,
Text,
TouchableOpacity,
View,
StyleSheet,
} from 'react-native';
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [contentStyle, setContentStyle] = useState({ visibility: 'hidden' });
const currentHeight = useRef(new Animated.Value(0)).current;
const [maxHeight, setMaxHeight] = useState(0);
const toggleContent = () => {
setIsOpen(!isOpen);
Animated.timing(currentHeight, {
toValue: isOpen ? 0 : maxHeight,
duration: 350,
}).start();
};
const getContentHeight = ({ nativeEvent }) => {
if (maxHeight !== 0) {
return;
}
const { height } = nativeEvent.layout;
setMaxHeight(height);
setContentStyle({visibility: 'visible', height: currentHeight });
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={toggleContent} style={styles.button}>
<Text>{'Open'}</Text>
</TouchableOpacity>
<Animated.View
style={[styles.content, contentStyle]}
onLayout={getContentHeight}>
<Text style={styles.paragraph}>
Change code in the editor and watch it change on your phone! Save to
get a shareable url.
</Text>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
backgroundColor: '#ffffff',
padding: 8,
},
button: {
justifyContent: 'center',
alignItems: 'center',
width: 120,
borderWidth: 1,
borderColor: 'black',
backgroundColor: '#c0c0c8',
padding: 10,
},
content: {
borderWidth: 1,
borderColor: 'blue',
overflow: 'hidden',
},
paragraph: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
export default App;
The key ingredient of detecting the maxHeight is in onLayout event of the Animated.View. Then some magic on displaying content through state handling.
And if you want to define maxHeight manually, then just rip the getContentHeight off.

React Native TouchableOpacity OnPress not Working with loop

I have a scrollview in which multiple items are generated with loop. I added TouchableOpacity above these items because i want these objects to be touchable. But when i add a method on onPress method it shows error not a function , is undefined
List_Data Component:
class List_Data extends React.Component {
fetchData = () => {
console.log("DONE");
}
_renderView = () => {
return (
<View style={{flex:1, padding: 20}}>
<View style={styles.container}>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} >
{
this.state.Data.map(function (data, index) {
return (
<TouchableOpacity key={index} onPress={() => this.fetchData()}>
<Image source={{uri: data.imageSrc}}
resizeMode={'cover'}
style={{width: '100%', height: imageHeight}}
/>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
</View>
)
}
render() {
return (
{this._renderView()}
);
}
}
I don't know whats the issue, it just a method which prints on console.
The issue is coming from your .map. Basically you are losing the value of this as you are not using an arrow function. If you change your .map(function(data, index) to .map((data,index) => it should work.
import * as React from 'react';
import { Text, View, StyleSheet, ScrollView, TouchableOpacity, Image } from 'react-native';
import { Constants } from 'expo';
export default class App extends React.Component {
state = {
Data: [
{imageSrc :'https://randomuser.me/api/portraits/men/39.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/38.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/men/37.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/36.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/men/35.jpg'},
{imageSrc: 'https://randomuser.me/api/portraits/women/34.jpg'},
]
}
// let's pass something so that we know that it is working
fetchData = (index) => {
alert(`you pressed ${index}`)
}
_renderView = () => {
return (
<View style={{flex: 1, padding: 20}}>
<View style={styles.container}>
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} >
{
this.state.Data.map((data, index) => { // change this to an arrow function
return (
<TouchableOpacity key={index} onPress={() => this.fetchData(index)}>
<Image source={{uri: data.imageSrc}}
resizeMode={'cover'}
style={{width: 100, height: 100}}
/>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
</View>
);
}
render() {
return (
this._renderView()
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
You can see it working in the following snack https://snack.expo.io/#andypandy/map-with-arrow-function
Try keyboardShouldPersistTaps={true} under ScrollView. <ScrollView keyboardShouldPersistTaps={true}>

Expo Camera only opening once with React Navigation

I set the Expo camera to open on the middle tab with react navigation. However, the camera only opens when I click on that tab the first time. If I switch off of it and go back it's just a black screen. Also the take a picture button is not there. (I am new with react native and kinda coding as a whole)
'use strict';
import React, { Component } from 'react';
import { createBottomTabNavigator } from 'react-navigation';
import { Camera, Permissions } from 'expo';
import {
AppRegistry,
Dimensions,
StyleSheet,
Text,
TouchableOpacity,
View,
Button
} from 'react-native';
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
</View>
);
}
}
class CameraView extends React.Component {
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
};
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
<TouchableOpacity
style={{
flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back,
});
}}>
<Text
style={{ fontSize: 18, marginBottom: 10, color: 'white' }}>
{' '}Flip{' '}
</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
}
}
class SettingsScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
}
}
export default createBottomTabNavigator({
Home: HomeScreen,
Camera:CameraView,
Settings: SettingsScreen
});
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
top: 250
},
capture: {
flex: 0,
backgroundColor: '#fff',
borderRadius: 5,
padding: 15,
paddingHorizontal: 20,
alignSelf: 'center',
margin: 20
}
});
With react navigation 5.x
import { useIsFocused } from '#react-navigation/native';
export const CameraView = (props) => {
const isFocused = useIsFocused();
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
useIsFocused Documentation
I had some issue.
This code solved it:
import { useIsFocused } from '#react-navigation/native';
function MyComponent() {
const isFocused = useIsFocused()
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
Old navigation
import { withNavigationFocus } from 'react-navigation'
render() {
const { isFocused } = this.props
return (
<View>
{ isFocused && <RNCamera ... /> }
</View>
)
}
export default withNavigationFocus(Camera)
To make this work you need to:
1.
import { NavigationEvents } from 'react-navigation';
state = { loaded: true }
render() {
const { loaded } = this.state;
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={payload => this.setState({loaded: true})}
onDidBlur={payload => this.setState({loaded: false})}/>
<View style={styles.cameraArea}>
{loaded && (
<Camera
type={Camera.Constants.Type.back}
ref={ref => {
this.camera = ref;
}}
/>
)}
</View>
The idea is to hide this camera view (onDidBlur-> loaded: false), then when you come back (onWillFocus is triggered and change loaded to true). When render() function is called it will show the <Camera /> again.
If you have any questions, feel free to ask.
This works for me. ( Navigation 5.x )
if you are using a different screen for CAMERA, you can easy unmount the screen when moving to another.
Reason of this behavior : Only one Camera preview can be active at any
given time. If you have multiple screens in your app, you should
unmount Camera components whenever a screen is unfocused.
<Stack.Screen name="camera" component={CameraScreen} options={{unmountOnBlur: true}}/>
</Stack.Navigator>
Documentation Link : https://docs.expo.io/versions/latest/sdk/camera/
Thanks.
I got it working by using NavigationEvents to determine if the tab is in focus and from there mount and unmount the camera. This also frees up the camera, if you need to use it in another screen. Here is what I would do in your example:
import { NavigationEvents } from 'react-navigation';
...
class CameraView extends React.Component {
constructor(props) {
super(props)
this.state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
isFocused:true
};
}
...
render(){
//...your existing if's
} if(this.state.isFocused === false){
return (
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
)
}
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
//...the rest of your camera code
}
I hope it works for you as well
I solved it using the hook useIsFocused from react-navigation/native. I tested it on Android, iOS and Web
import { useIsFocused } from '#react-navigation/native';
...
const isFocused = useIsFocused();
...
return (
isFocused && (
<Camera
ref={(ref) => {
camera = ref;
}}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
)
);
If you are using RN Expo with React Navigation - Tab Navigator.
Then just use unmountOnBlur - unmountOnBlur Documentation
This will force the camera to unmount on every navigation focus changes.
in Expo react native
import { useIsFocused } from '#react-navigation/native';
const isFocused = useIsFocused();
{isFocused &&
<Camera
type={type}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{ flex: 1 }}
flashMode={flash}
zoom={zoom}
/>
}

Resources