react-native-reanimated, pass component as prop with animated close - reactjs

I have a component which uses react-native-reanimated, how can I add hideSheet() to the component I am passing as a prop?
import Animated, { Easing } from "react-native-reanimated";
...
const BottomSheet = ({
children,
...
renderHeader,...
}) => {
const style = useMemo(
() => getStyleObj({ backgroundColor, secondSnapshot }),
[backgroundColor, secondSnapshot]
);
const [alignment] = useState(new Animated.Value(0));
const [alignmentChildren] = useState(new Animated.Value(0));
const [open, setOpen] = useState(true);
const WrapperComponent = tapToOpenEnabled ? Pressable : View;
const openSheet = () => {
Animated.timing(alignment, {
toValue: 1,
duration: 400,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}).start();
};
const hideSheet = () => {
Animated.timing(alignment, {
toValue: 0,
duration: 400,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}).start();
};
const toggleOpen = () => {
if (open) {
hideSheet();
setOpen(false);
} else {
openSheet();
setOpen(true);
}
};
I am passing the header component as a prop:
const renderSheetHeader = useCallback(() => {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Icon name="closecircle" size={20} color={colors.GREYONE} *add hideSheet here* />
</View>
);
}, []);
return (
<BottomSheet
...
renderHeader={renderSheetHeader()}
...
>
return (
<WrapperComponent>
...
{renderHeader}

You could pass it as a parameter to your function.
const renderSheetHeader = useCallback((onHide) => {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Pressable onPress={() => onHide()}>
<Icon name="closecircle" size={20} color={colors.GREYONE} />
</Pressable>
</View>
</View>
);
}, []);
Then, call it as usual.
<WrapperComponent>
...
{(onHide) => renderHeader(onHide)}
In your WrapperComponent:
renderHeader(hideSheet)
However, if it is not necessary that you define this as a function (and I do not see a reason in your current code), then you should be better of to just create a normale JSX component.
export function SheetHeader(props) {
return (
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<View style={styles.headerCont}>
<Text style={styles.headerTXT}>Setup your reminders</Text>
<Pressable onPress={() => onHide()}>
<Icon name="closecircle" size={20} color={colors.GREYONE} />
</Pressable>
</View>
</View>
);
}
and call it in your WrapperComponent directly.
<SheetHeader onHide={hideSheet} />

Related

React Native onPress doesn't work on IOS, but on Web it does

community!
I am building my first React-Native app and I have problem running this part of the code on IOS.
There is a function passed to onPress prop of a Button, that works on web but when I tried it on IOS nothing happends when pressing the button. The function should update the state of the parent component so it render's the next part of the Game form.
(There is a complete screen code sample at the bottom and you can also check the entire app in this git repository: https://github.com/dogaruemiliano/claim-app)
const handlePress = () => {
handleSubmit(maxPoints)
}
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title="Next"
onPress={() => handlePress()} // here is the problem
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
The function called in the handlePress,
handleSubmit(maxPoints)
is received in the props of the component from the parent component that handles the logic up to the point where it send a POST request to the API.
Here is the complete code of the screen for New Game Creation.
import React, { useRef, useState, useEffect } from 'react'
import { connect } from 'react-redux'
import { SafeAreaView, View, Text, StyleSheet, Button, Platform, KeyboardAvoidingView } from 'react-native'
import { TextInput, TouchableOpacity } from 'react-native-gesture-handler'
import Navbar from '../components/Navbar'
import COLORS from '../constants/Colors'
import { createGame } from '../actions'
const MaxPointsInput = (props) => {
const { handleSubmit } = props
const [maxPoints, setMaxPoints] = useState("")
const [done, setDone] = useState(false)
const inputRef = useRef(null)
const handlePress = () => {
setDone(true) // to test if function is executed
handleSubmit(maxPoints)
}
useEffect(() => {
inputRef.current.focus()
}, [])
return (
<>
<Text style={styles.header}>Max points</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={maxPoints}
onChangeText={value => setMaxPoints(value)}
keyboardType="numeric"
/>
<KeyboardAvoidingView
behavior='padding'
>
<View>
<TouchableOpacity style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={() => handlePress()}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</>
)
}
const PlayersInput = (props) => {
const { handleSubmit, currentUser } = props
const [players, setPlayers] = useState([])
const [nameInputValue, setNameInputValue] = useState("")
const inputRef = useRef()
useEffect(() => {
inputRef.current.focus()
}, [])
const addPlayer = () => {
setNameInputValue("")
setPlayers([...players, nameInputValue])
inputRef.current.value = ""
inputRef.current.focus()
}
return (
<>
<Text style={styles.header}>Players</Text>
<View style={{ flex: 1 }}>
<Text>Player #1: {currentUser.attributes.name} (you)</Text>
{players.map((player, index) => {
return(
<Text key={player}>Player #{index + 2}: {player}</Text>
)
})}
<Text>Player #{players.length + 2}</Text>
<TextInput
style={styles.input}
ref={inputRef}
value={nameInputValue}
onChangeText={value => setNameInputValue(value)}
keyboardType="numeric"
/>
</View>
<KeyboardAvoidingView
behavior='padding'
style={{ marginBottom: 200, flex: 1 }}
>
<TouchableOpacity style={styles.btn}>
<Button
title="Add"
onPress={addPlayer}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
<TouchableOpacity style={styles.btn}>
<Button
title="Done"
onPress={() => handleSubmit(players)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</TouchableOpacity>
</KeyboardAvoidingView>
</>
)
}
const Confirmation = (props) => {
const { handleSubmit, maxPoints, players, currentUser } = props
return (
<>
<Text style={styles.header}>Details</Text>
<View style={{ flex: 1 }}>
<Text>Max points: {maxPoints}</Text>
<Text>Players: </Text>
<Text>1. {currentUser.attributes.name}</Text>
{players.map((player, index) => {
return (
<Text key={player}>{index + 2}. {player}</Text>
)
})}
</View>
<KeyboardAvoidingView
behavior='padding'
>
<View style={styles.btn}>
<Button
title="Confirm"
onPress={() => handleSubmit({maxPoints, players}, currentUser)}
color={Platform.OS === 'ios' ? COLORS.white : COLORS.black} />
</View>
</KeyboardAvoidingView>
</>
)
}
function NewGameScreen(props) {
const { navigation, currentUser, createGame } = props
const [maxPoints, setMaxPoints] = useState("")
const [players, setPlayers] = useState([])
const [showMaxPointsInput, setShowMaxPointsInput] = useState(true)
const [showPlayersInput, setShowPlayersInput] = useState(false)
const [showConfirmation, setShowConfirmation] = useState(false)
const handleMaxPointsSubmit = (value) => {
setMaxPoints(value)
setShowMaxPointsInput(false)
setShowPlayersInput(true)
}
const handlePlayersSubmit = (value) => {
setPlayers(value)
setShowPlayersInput(false)
setShowConfirmation(true)
}
const handleConfirmaton = (data, currentUser) => {
createGame({data, currentUser, navigation})
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.inner}>
{ showMaxPointsInput &&
<MaxPointsInput handleSubmit={handleMaxPointsSubmit}/>
}
{ showPlayersInput &&
<PlayersInput handleSubmit={handlePlayersSubmit} currentUser={currentUser} />
}
{
showConfirmation &&
<Confirmation maxPoints={maxPoints} currentUser={currentUser} players={players} handleSubmit={handleConfirmaton} />
}
</View>
<Navbar navigation={navigation} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 'auto',
marginTop: 48
},
inner: {
padding: 24,
paddingBottom: 80,
justifyContent: "center",
width: (Platform.OS === 'web' ? 300 : null),
marginTop: 48,
},
btn: {
borderRadius: 6,
marginTop: 12,
marginBottom: 2,
backgroundColor: COLORS.black,
},
btnOutline: {
borderRadius: 6,
marginTop: 12,
backgroundColor: COLORS.transparent,
borderWidth: 2,
borderColor: COLORS.black,
color: COLORS.black,
},
header: {
fontSize: 32,
textAlign: 'center',
marginBottom: 32,
},
scrollView: {
paddingHorizontal: 20,
},
input: {
textAlign: 'center',
height: 50,
fontSize: 32,
marginBottom: 20,
borderBottomWidth: 2,
borderColor: '#dbdbdb',
paddingBottom: 5,
},
})
const mapStateToProps = (state) => {
return {
currentUser: state.currentUser
}
}
const mapDispatchToProps = (dispatch) => {
return {
createGame: (props) => dispatch(createGame(props))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NewGameScreen)
Thank you for reading!
From your code block, it seems that you are wrapping a button inside a TouchableOpacity Component which is imported from react-native-gesture-handler.
You can refactor code into, like removing unwanted wrapping with Touchable Elements if not required.
<View style={styles.btn}>
<Button
title={done ? "Done..." : "Next"}
onPress={handlePress}
color={Platform.OS === 'ios' ? 'white' : 'black'} />
</View>
Import TouchableOpacity from react-native
import { Text, View, StyleSheet ,TextInput, TouchableOpacity} from 'react-native';
Happy Coding!

How to update component from another one in react native

I am using React Native App, I have a main page that displays the categories, and a button that navigates to another page to add a new category,
the issue is that when I add a new one I want to update the main page as well to include the new one,
is there a way to do so, this my code,
this the navigation
const HomeStack = createStackNavigator();
const Tab = createMaterialBottomTabNavigator();
const MainTabScreen = () => (
<Tab.Navigator
initialRouteName="Home"
activeColor="#fff"
>
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
tabBarLabel: 'Home',
tabBarColor: '#009387',
tabBarIcon: ({ color }) => (
<Icon name="ios-home" color={color} size={26} />
),
}}
/>
);
export default MainTabScreen;
const HomeStackScreen = ({navigation}) => (
<HomeStack.Navigator screenOptions={{
headerStyle: {
backgroundColor: '#009387',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}>
<HomeStack.Screen name="Home" component={HomeScreen} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
<HomeStack.Screen name="Home_Provider" component={HomeScreen_Provider} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
<HomeStack.Screen name="AddServiceScreen" component={AddServiceScreen} options={{
title:'Overview',
headerLeft: () => (
<Icon.Button name="ios-menu" size={25} backgroundColor="#009387" onPress={() => navigation.openDrawer()}></Icon.Button>
)
}} />
</HomeStack.Navigator>
);
this is the home page:
const HomeScreen_Provider = ({navigation}) => {
const [services, setServices] = React.useState([]);
const [status, setStatus] = React.useState([]);
async function fetchdata(){
api.GetMyServices({headers:{'Accept': "application/json", 'content-type': "application/json"}})
.then(function (response) {
console.log(response.data);
setServices(response.data);
})
.catch(function (error) {
console.error(error);
});
}
React.useEffect(() => {
fetchdata();
}, []);
return (
<View style={styles.container}>
<StatusBar backgroundColor='#009387' barStyle="light-content"/>
<View style={{marginTop:20}}>
<Text style={styles.text_header}>My Services</Text>
</View>
<TouchableOpacity
style={{alignItems:'flex-end',marginLeft:250}}
onPress={() => navigation.navigate('AddServiceScreen')}
>
<Text style={styles.button}>Add New..</Text>
</TouchableOpacity>
{services.length == 0 ? <Text >You Have No Services Yet, Click Here To Add..</Text> : <View/>}
<ScrollView>
{services.map (service => (
<View style ={styles.rowContainer}>
<Text style={styles.text_footer}>{service.name} </Text>
<Text style={styles.text_footer}>{service.status} </Text>
<TouchableOpacity
onPress={handleToggle}
>
<Text style={styles.button}>Change Status</Text>
</TouchableOpacity>
</View>
))}
</ScrollView>
</View>
)
};
export default HomeScreen_Provider;
````````````
here is the add new page :
const AddServiceScreen = ({navigation}) => {
const { colors } = useTheme();
const [availableServices, setAvailableServices] = React.useState([]);
const [userID, setUserID] = React.useState(0);
const [serviceId, setServiceId] = React.useState(0);
const [serviceName, setServiceName] = React.useState(0);
const [serviceStatus, setServiceStatus] = React.useState('');
const [serviceCost, setServiceCost] = React.useState(0);
async function handleAddServiceButton () {
api.AddNewCategory(Category,{headers:{'Accept': "application/json", 'content-type': "application/json"}})
.then(function (response) {
})
.catch(function (error) {
console.error(error);
});
}
return (
<View style={styles.container}>
<StatusBar barStyle= { theme.dark ? "light-content" : "dark-content" }/>
<View style={styles.rowContainer} >
{availableServices.length !== 0 ? (
<View>
<Text style={{marginTop:15,marginBottom:30}}>Select From Existing Services</Text>
<Picker
selectedValue={serviceId}
style={{ height: 50, width: 150 }}
onValueChange={(itemValue, itemIndex) => setServiceId(itemValue)}
>
{availableServices.map((service)=>{
return(
<Picker.Item label={service.name} value={service.id} />
)
})}
</Picker>
</View>
): <View/>}
</View>
<Button
title="Submit"
onPress={handleAddServiceButton}
/>
</View>
);
};
export default AddServiceScreen;
You can either use react context to update the state directly and use the spread operator in the API call
or
You can maintain your state using redux to centralize your application state

Invariant Violation: Text strings must be rendered within a <Text> component while using flatList

I am using flat list to display data which is coming from unsplash api. But here it keeps on complaining to saying this
Invariant Violation: Text strings must be rendered within a component
I am not even using any text component. I have no idea what is wrong here.
App.js
export default function App() {
const [loading, setLoading] = useState(true);
const [image, setImage] = useState([]);
const {height, width} = Dimensions.get('window');
const URL = `https://api.unsplash.com/photos/random?count=30&client_id=${ACCESS_KEY}`;
useEffect(() => {
loadWallpapers();
}, [])
const loadWallpapers =() => {
axios.get(URL)
.then((res) => {
setImage(res.data);
setLoading(false);
}).catch((err) => {
console.log(err)
}).finally(() => {
console.log('request completed')
})
}
const renderItem = (image) => {
console.log('renderItem', image);
return (
<View style={{height, width}}>
<Image
style={{flex: 1, height: null, width: null}}
source={{uri : image.urls.regular}}/>
</View>
)
}
return loading ? (
<View style={{flex: 1, backgroundColor: 'black', justifyContent: 'center',alignItems: 'center'}}>
<ActivityIndicator size={'large'} color="grey"/>
</View>
): (
<SafeAreaView style={{flex: 1, backgroundColor: 'black'}}>
<FlatList
horizontal
pagingEnabled
data={image}
renderItem={({ item }) => renderItem(item)} />}
/>
</SafeAreaView>
)
}
I thing data of Flatlist is null, try
<FlatList
horizontal
pagingEnabled
data = {image ? image : []}
renderItem={({ item }) => renderItem(item)} />}
/>
I needed to do something like this to make it work.
const renderItem = ({ item }) => { <---- I have destructured item here
console.log(item)
return (
<View style={{ flex: 1 }}>
</View>
);
};
<FlatList
scrollEnabled={!focused}
horizontal
pagingEnabled
data={image}
renderItem={renderItem}
/>

this.refs x useRef (Is there any similarity?)

I'm trying to use a library that uses this.ref, but I have to pass it to hooks. I'm not getting it.
Original code:
import ViewShot from "react-native-view-shot";
class ExampleCaptureOnMountManually extends Component {
componentDidMount () {
this.refs.viewShot.capture().then(uri => {
console.log("do something with ", uri);
});
}
render() {
return (
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
<Text>...Something to rasterize...</Text>
</ViewShot>
);
}
}
My hook code:
export default function screenshot() {
const refs = useRef();
refs.viewShot.capture().then(uri => {
console.log('do something with ', uri);
});
return (
<View style={styles.container}>
<View style={styles.header} />
<ViewShot ref="viewShot" options={{format: 'jpg', quality: 0.9}}>
<View>
<Text>Hello World</Text>
</View>
</ViewShot>
<View style={styles.footer}>
<Button title="print" onPress={onCapture} />
</View>
</View>
);
}
Link Lib:
https://github.com/gre/react-native-view-shot
With useRef(), you don't do const refs = useRef();, you declare the ref:
const viewShot = useRef();
And then pass it in the ref attribute:
<ViewShot ref={viewShot} ...
You should now use it as viewShot.current.
Nevertheless, since your original code executed in componentDidMount, now you should also employ useEffect (notice the addition of .current):
useEffect(() => {
viewShot.current.capture().then(uri => {
console.log('do something with ', uri);
});
}, [])
Therefore:
export default function screenshot() {
const viewShot = useRef();
useEffect(() => {
viewShot.current.capture().then(uri => {
console.log('do something with ', uri);
});
}, [])
return (
<View style={styles.container}>
<View style={styles.header} />
<ViewShot ref={viewShot} options={{format: 'jpg', quality: 0.9}}>
<View>
<Text>Hello World</Text>
</View>
</ViewShot>
<View style={styles.footer}>
<Button title="print" onPress={onCapture} />
</View>
</View>
);
}

Invalid use of hooks when calling component with onPress

I'm trying to work with modals when I click on a button from the header.
Say I have this component List, and List is using custom navigation options:
import { CustomModal } from './components/Modal';
const List = (props) => {
const [enteredUrl, setEnteredUrl] = useState('');
const urlInputHandler = (enteredUrl) => {
setEnteredUrl(enteredUrl);
};
const addUrlHander = () => {
console.log(enteredUrl);
}
return (
<View></View>
);
};
List.navigationOptions = (navData) => {
return {
headerTitle: 'Workouts',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Add'
iconName='md-add'
onPress={() => {
CustomModal(); //here is the modal
}}
/>
</HeaderButtons>
),
headerBackTitle: null
};
};
My Modal component has this:
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return (
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
);
}
But it is giving me the invalid hook error. Why is it that my onPress in my navigationOptions giving me this? Am I doing this wrong?
onPress is a callback, you can't put components in it. Probably what you want is something like this:
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<CustomModal/>
</HeaderButtons>
and the modal looks like
export const CustomModal = (props) => {
const [modalVisible, setModalVisible] = useState(false);
console.log(props);
return modalVisible?(
<Modal
animationType='slide'
transparent={false}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}
>
<View style={{ marginTop: 22 }}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
):(
<Item
title='Add'
iconName='md-add'
onPress={() => setModalVisible(!modalVisible)}
/>
);
}

Resources