In my React Native app, I want to embed a time picker inside of a bottom sheet. Currently the time picker displays when tapped using onPress={showTimepicker} .
TimePicker.js
import React, { useState, useRef } from "react";
import RBSheet from "react-native-raw-bottom-sheet";
import { Text, View, TouchableOpacity, Platform } from "react-native";
import NotificationOff from "../../../components/ImageComponents/NotificationIcons/NotificationOff";
import DateTimePicker from "#react-native-community/datetimepicker";
import Animated from "react-native-reanimated";
const DailyNotification = () => {
const refRBSheet = useRef();
let fall = new Animated.Value(1);
const animatedShadowOpacity = Animated.interpolateNode(fall, {
inputRange: [0, 1],
outputRange: [0.5, 0],
});
const [isEnabled, setIsEnabled] = useState(false);
const toggleSwitch = () => setIsEnabled((previousState) => !previousState);
const [date, setDate] = useState(new Date(1598051730000));
const [mode, setMode] = useState("date");
const [show, setShow] = useState(false);
const onChange = (event, selectedDate) => {
const currentDate = selectedDate || date;
setShow(Platform.OS === "ios");
setDate(currentDate);
};
const showMode = (currentMode) => {
setShow(true);
setMode(currentMode);
};
return (
<View style={styles.userAreaContainer}>
<TouchableOpacity onPress={() => refRBSheet.current.open()}>
<NotificationOff />
</TouchableOpacity>
<RBSheet
ref={refRBSheet}
animationType="fade"
height={500}
closeOnDragDown={true}
closeOnPressMask={false}
customStyles={{
wrapper: {
backgroundColor: "transparent",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.8,
shadowRadius: 3.84,
elevation: 5,
},
draggableIcon: {
backgroundColor: "#000",
},
}}
>
<View style={styles.notifButtonWrapper}>
<View style={{ width: "100%" }}>
<TouchableOpacity onPress={showTimepicker}>
<Text> Set Notification Time </Text>
</TouchableOpacity>
{show && (
<DateTimePicker
testID="dateTimePicker"
value={date}
mode="time"
is24Hour={true}
display="inline"
onChange={onChange}
/>
)}
</View>
<TouchableOpacity style={styles.saveButton}>
<Text style={styles.link}>Save</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Cancel</Text>
</TouchableOpacity>
</View>
</RBSheet>
</View>
);
};
export default DailyNotification;
This old S/O post describes how it is done in Android, but I haven't been able to find a React Native example.
I've seen this done in other apps (example below) but have not been able to figure out how to do this. Any ideas or libraries that already have this capability built in? Thanks in advance.
You can use react-native-date-picker package. It has two different modes, modal and inlined. You can put the inlined version inside a view. As far as I understand, you don't want to see timepicker as a modal, you want to see it as inlined/embedded. Check the inlined usage of the package. This should solve your problem.
Related
I am trying to add pagination function for the web in React Native. Essentially I have two issues.
I have linked the data to the page numbers, so it is calculating the number of pages correctly, but the pages don't change when I hit the arrow to the next page.
The page selection dropdown doesn't work. I can change the number in the code to show a specific number of items on the page, but not through the dropdown on the page.
Page Numbers changing when I manually change how many to display per page
I've included some of the code below that relates to this.
import React, { useEffect, useState } from 'react';
import { StyleSheet, ActivityIndicator, Text, View, FlatList, Modal, Dimensions } from 'react-native';
import Moment from 'react-moment';
import { DataTable } from 'react-native-paper';
import { AntDesign } from '#expo/vector-icons';
import TaskView from '../components/TaskView';
import { Switch } from 'react-native-paper';
import DropDownPicker from 'react-native-dropdown-picker'
import { RFValue } from "react-native-responsive-fontsize";
const window = Dimensions.get("window");
const screen = Dimensions.get("screen");
const numberOfItemsPerPageList = [1, 5, 10, 25, 50, 100];
function TaskList(props) {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [dimensions, setDimensions] = useState({ window, screen });
const [id, setId] = useState('');
const moment = require('moment');
const [toggle, setToggle] = useState(false);
const [items, setItems] = useState([]);
const [value, setValue] = useState([]);
const [open, setOpen] = useState(false);
const [page, setPage] = useState(1);
const [numberOfItemsPerPage, onItemsPerPageChange] = useState(numberOfItemsPerPageList[1]);
const [currentPage, setCurrentPage] = useState(1);
const indexOfLastItem = currentPage * numberOfItemsPerPage;
const indexOfFirstItem = indexOfLastItem - numberOfItemsPerPage;
const currentItems = dataSet.slice(indexOfFirstItem, indexOfLastItem);
const dataLength = filteredData.length;
const totalPages = Math.ceil(dataLength / numberOfItemsPerPage);
return (
<View style={styles.mainContainer}>
<View style={styles.switchContainer}>
<Text style={styles.switchText}>Future Defer Dates:</Text>
<Switch value={toggle} onValueChange={toggleSwitch} />
</View>
<DropDownPicker
placeholder='Filter'
items={items}
open={open}
setOpen={setOpen}
value={value}
setValue={setValue}
multiple={true}
mode='BADGE'
badgeDotColors={["blue", "green", "red", "orange", "pink", "yellow", "brown", "violet"]}
onClose={filterData}
/>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
>
<View style={styles.modal}>
<View style={styles.closeCircle}>
<AntDesign name="closecircleo" size={26} color="black" onPress={() => setModalVisible(false)} />
</View>
<TaskView task={id} />
</View>
</Modal>
<DataTable>
**<DataTable.Pagination
page={page}
// onClick={}
totalItems={dataLength}
numberOfPages={totalPages}
onPageChange={page => setCurrentPage(currentPage+1)}
selectPageDropdown={numberOfItemsPerPageList}
label={`${currentPage} of ${totalPages} pages`} //text # of # pages
selectPageDropdownLabel={'Tasks per page'} //text next to task amount number drop down
numberOfItemsPerPageList={numberOfItemsPerPageList} //task amount number drop down
showFastPaginationControls //arrows to the first and last page
numberOfItemsPerPage={numberOfItemsPerPage}
onItemsPerPageChange={onItemsPerPageChange}
/>**
<DataTable.Header style={{ position: 'sticky', top: '0px', backgroundColor: "#eaeaea", zIndex: 1 }}>
<DataTable.Title><Text style={{ fontSize: 30, fontWeight: 'bold', position: 'absolute', color: 'black' }}>Task</Text></DataTable.Title>
<DataTable.Title><Text style={{ fontSize: 30, fontWeight: 'bold', position: 'absolute', color: 'black' }}>Due Date</Text></DataTable.Title>
<DataTable.Title><Text style={{ fontSize: 30, fontWeight: 'bold', position: 'absolute', color: 'black' }}>Status</Text></DataTable.Title>
<DataTable.Title><Text style={{ fontSize: 30, fontWeight: 'bold', position: 'absolute', color: 'black' }}>Category</Text></DataTable.Title>
</DataTable.Header>
{isLoading ? <ActivityIndicator /> : (
<FlatList
data={filterData()}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<DataTable.Row onPress={() => showModal(item.id)}>
<DataTable.Cell>
<AntDesign name="rightsquare" size={14} color="black" style={{ marginRight: 15 }} />
{item.name}
</DataTable.Cell>
<DataTable.Cell>
<Moment format="MMMM Do YYYY">{item.due_date}</Moment>
</DataTable.Cell>
<DataTable.Cell>
{item.status.status}
</DataTable.Cell>
<DataTable.Cell>
{item.category.name}
</DataTable.Cell>
</DataTable.Row>
)
}
/>
)}
</DataTable>
</View>
);
};
/>
I've been trying to pass up this prop from CameraButton.js file that gives the UI of an image that was taken but whenever I activate the prop in the AddPost.js, it gives me all the values but when I try to get the singular value of the image like using console.log(props.route.params.image) and gives error undefined is not an object
enter image description here
but it works perfectly when export default function console.log(props.route.params) and shows
enter image description here
AddPost.JS
import { useNavigation } from "#react-navigation/core";
import React from 'react'
import {useState} from "react";
import { View, TextInput, Button } from 'react-native'
export default function AddPost(props) {
console.log(props);
const navigation = useNavigation();
const [caption, setCaption] = useState("")
const uploadImage = async () => {
const response = await fetch(uri)
}
return (
<View style={{flex: 1}}>
<TextInput
placeholder="Whats on your mind Edgers navars"
onChangeText={(caption) => setCaption(caption)}
/>
<Button title = "Take A Photo" onPress={() => navigation.navigate("CameraButton")}
/>
<Button title = "Save" onPress={() => uploadImage()}
/>
</View>
)
}
CameraButton.Js
import { Camera, CameraType } from 'expo-camera';
import { useNavigation } from "#react-navigation/core";
import { useState } from 'react';
import { Button, StyleSheet, Text, TouchableOpacity, View, Image } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function App() {
const navigation = useNavigation();
const [type, setType] = useState(Camera.Constants.Type.back)
const [permission, requestPermission] = Camera.useCameraPermissions();
const [image, setImage] = useState(null);
const [camera, setCamera] = useState(null);
const takePicture = async () => {
if(camera){
const data = await camera.takePictureAsync(null);
setImage(data.uri);
}
}
if (!permission) {
// Camera permissions are still loading
return <View />;
}
if (!permission.granted) {
// Camera permissions are not granted yet
return (
<View style={styles.container}>
<Text style={{ textAlign: 'center' }}>
We need your permission to show the camera
</Text>
<Button onPress={requestPermission} title="grant permission" />
</View>
);
}
function toggleCameraType() {
setType((current) => (
current === Camera.Constants.Type.back ? Camera.Constants.Type.front : Camera.Constants.Type.back
));
}
// No permissions request is necessary for launching the image library
let openImagePickerAsync = async () => {
let permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
alert("Permission to access camera roll is required!");
return;
}
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
}
}
return (
<View style={styles.container}>
<Camera ref={ref => setCamera(ref)} style={styles.camera} type={type}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={toggleCameraType}>
<Text style={styles.text}>Flip Camera</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => takePicture()}>
<Text style={styles.text}>Take Picture</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={openImagePickerAsync}>
<Text style={styles.text}>Choose Picture</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('AddPost', {image})}>
<Text style={styles.text}>Save Picture</Text>
</TouchableOpacity>
</View>
</Camera>
{image &&<Image source={{uri: image}}style={styles.camera}/>}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
camera: {
flex: 1,
},
buttonContainer: {
flex: 1,
flexDirection: 'row',
backgroundColor: 'transparent',
margin: 64,
},
button: {
flex: 1,
alignSelf: 'flex-end',
alignItems: 'center',
},
text: {
fontSize: 24,
fontWeight: 'bold',
color: 'white',
},
});
You have to get the uri from the route object.
const response = await fetch(props.route.params?.image)
In you file CameraButton.js set the navigation for this:
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('AddPost', {
image: image
})}>
<Text style={styles.text}>Save Picture</Text>
</TouchableOpacity>
Be sure that the state image contains only the uri and not and object
Try props[0].route.params.image.
I'm having a in react native which cannot call function component of gorhom/bottom-sheet and import to another component. Below is my code and error.
Function Component
import React, {useCallback, useMemo, useRef} from 'react';
import {View, Text, StyleSheet, Button} from 'react-native';
import {BottomSheetModal, BottomSheetModalProvider} from '#gorhom/bottom-sheet';
const BottomModal = () => {
const snapPoints = useMemo(() => ['25%', '50%'], []);
// ref
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// variables
// callbacks
const handlePresentModalPress = useCallback(() => {
bottomSheetModalRef.current?.present();
}, []);
const handleSheetChanges = useCallback((index: number) => {
console.log('handleSheetChanges', index);
}, []);
// renders
return (
<BottomSheetModalProvider>
<View style={styles.container}>
<Button
onPress={handlePresentModalPress}
title="Present Modal"
color="black"
/>
<BottomSheetModal
ref={bottomSheetModalRef}
index={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}>
<View style={styles.contentContainer}>
<Text>Awesome 🎉</Text>
</View>
</BottomSheetModal>
</View>
</BottomSheetModalProvider>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
justifyContent: 'center',
backgroundColor: 'grey',
},
contentContainer: {
flex: 1,
alignItems: 'center',
},
});
export default BottomModal;
Import it to use in another function component
<TouchableOpacity onPress={BottomModal}>
<Icon
size={28}
style={{marginRight: 20, color: Colors.grey2, marginTop: 16}}
name="calendar-outline"
/>
</TouchableOpacity>
Error
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happend for one of the following reasons
The onPress function for the TouchableOpacity seems to be a problem here. Use some state to show or hide the BottomModel accordingly
const [isBottomModalOpen, setIsBottomModalOpen] = useState(false);
And then for the Touchable Opacity you set the state to be true and render the Modal
<TouchableOpacity onPress={BottomModal}>
<Icon
size={28}
style={{marginRight: 20, color: Colors.grey2, marginTop: 16}}
name="calendar-outline"
/>
</TouchableOpacity>
And then render the Modal conditionally if the setIsBottomModalOpen state is set to true
I've had a problem when i used useState(). i have to filter by searched words on my data and list.
i need to define my data list with State (i'd list with searched words) but when i use State, i've taken 'Invalid Hook' error.
let [list, setList] = useState(data);
//called
data={list}
I don't find where i use that , I couldn't fix for 3 days, i can't reach next step :( I hope i'll fix with expert helps...
import React, {Component, useState} from 'react'
import {
Text,
StyleSheet,
View,
FlatList,
SafeAreaView,
ScrollView,
Image,
TextInput,
} from 'react-native'
import data from '../../data'
export default class Flatlistexample extends Component {
render () {
//defined below
let [list, setList] = useState(data);
seachFilter=(text)=>{
const newData = data.filter(item=>{
const listitem= `${item.name.toLowerCase()} ${item.company.toLowerCase()}`;
return listitem.indexOf(text.toLowerCase())
})
};
return (
<SafeAreaView
style={{
flex: 1,
}}>
<FlatList
//called
data={list}
renderItem={({item, index})=>{
return (
<ScrollView>
<SafeAreaView
style={[
styles.container,
{backgroundColor: index % 2 === 0 ? '#fafafa' : '#bbb'},
]}>
<Image style={styles.profile} source={{uri: item.picture}} />
<View style={styles.rightside}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.company}>{item.company}</Text>
</View>
</SafeAreaView>
</ScrollView>
)
}}
keyExtractor={item => item._id}
ListHeaderComponent={() => {
const [search, setSearch] = useState('');
return (
<View style={styles.seachContainer}>
<TextInput
style={styles.textInput}
placeholder={'Search...'}
value={search}
onChangeText={text=>{
setSearch(text)
}}
></TextInput>
</View>
)
}}
/>
</SafeAreaView>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
borderColor: 'gray',
},
profile: {
width: 50,
height: 50,
borderRadius: 25,
marginLeft: 10,
},
rightside: {
marginLeft: 20,
justifyContent: 'space-between',
marginVertical: 5,
},
name: {
fontSize: 22,
marginBottom: 10,
},
searchContainer: {
padding: 10,
borderWidth: 2,
borderColor: 'gray',
},
textInput: {
fontSize: 16,
backgroundColor: '#f9f9f9',
padding: 10,
},
})
Thank you
React hooks can be used with functional component only, here you are using class component
You need to understand the difference between functional component and class component first.
Here you are using class component so your state should be manageed in the following way
export default class Flatlistexample extends Component {
constructor(props)
{
this.state={list:[]}
}
}
and to update list
this.setState({list: <array of data>})
If you want to use hooks, your component needs to be changed something like the following:
const Flatlistexample = () => {
//defined below
let [list, setList] = useState(data);
seachFilter = (text) => {
const newData = data.filter(item => {
const listitem = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`;
return listitem.indexOf(text.toLowerCase())
})
};
return (
<SafeAreaView
style={{
flex: 1,
}}>
<FlatList data={list} renderItem={Your flatlist Item}/>
</SafeAreaView>
)
}
export default Flatlistexample
Here you go, I've added lots of comments. I hope you find this instructive. Let me know if you have questions!
import React, { useMemo, useState } from 'react'
import {
Text,
StyleSheet,
View,
FlatList,
SafeAreaView,
ScrollView,
Image,
TextInput,
} from 'react-native'
import data from '../../data'
// changed this to a functional component so you can use hooks. You can't use hooks in class components.
const Flatlistexample = () => {
// you don't actually need to `useState` for your list, since you're always just filtering `data`
// you would need to use `useState` if you were receiving data from an API request, but here it's static
const [search, setSearch] = useState('') // this should live in the main component so you can filter the list
const parsedSearch = search.toLowerCase() // do this once outside the filter, otherwise you're converting it for each item in the data array
const filteredList = useMemo(
() =>
data.filter(item => {
const itemText = `${item.name.toLowerCase()} ${item.company.toLowerCase()}`
return itemText.indexOf(parsedSearch) > -1 // returns `true` if search is found in string
}),
[parsedSearch], // this will only run if parsedSearch changes
)
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList
//called
data={filteredList} // use the filtered list here
renderItem={({ item, index }) => {
return (
<ScrollView>
<SafeAreaView
style={[
styles.container,
{ backgroundColor: index % 2 === 0 ? '#fafafa' : '#bbb' },
]}
>
<Image style={styles.profile} source={{ uri: item.picture }} />
<View style={styles.rightside}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.company}>{item.company}</Text>
</View>
</SafeAreaView>
</ScrollView>
)
}}
keyExtractor={item => item._id}
ListHeaderComponent={() => {
return (
<View style={styles.seachContainer}>
<TextInput
style={styles.textInput}
placeholder={'Search...'}
value={search}
onChangeText={text => {
setSearch(text)
}}
/>
</View>
)
}}
/>
</SafeAreaView>
)
}
export default Flatlistexample
I have a TextInput, when the text is onFocus, I want to display a DateTimePicker component.
I have a AddMeeting.js component, which contains the TextInput, and another component for the DateTimePicker itself.
I thought about having a state (boolean) in AddMeeting that would change when TextInput is getting onFocus / onBlur and with conditional rendering to display / dismiss the DateTimePicker from the screen.
the problem is, I already have a show and hide functions in the DateTimePicker component itself, and it seems redundant to have the state in AddMeeting too.
This is the DateimePicker:
import React, { useState } from "react";
import { View } from "react-native";
import DateTimePickerModal from "react-native-modal-datetime-picker";
const CustomDateTimePicker = () => {
const [isPickerVisible, setPickerVisibility] = useState(false);
const [chosenDate, setChosenDate] = useState("");
const showPicker = () => {
setPickerVisibility(true);
};
const hidePicker = () => {
setPickerVisibility(false);
};
const handleConfirm = (date) => {
setChosenDate = date
console.warn("A date has been picked: ", date);
hidePicker();
}
return (
<View>
<DateTimePickerModal
isVisible={isPickerVisible}
mode="datetime"
onConfirm={handleConfirm}
onCancel={hidePicker}
/>
</View>
)
}
export default CustomDateTimePicker;
This is the AddMeeting.js:
const AddMeetingScreen = ({ navigation }) => {
_popAddMeeting = () => {
navigation.pop()
}
_showPicker = () => {
console.log("SHOWING PICKER");
}
return (
<>
<SafeAreaView style={styles.addMeetingContainer}>
<View style={styles.headerContainer}>
<TouchableOpacity onPress={_popAddMeeting} style={styles.backButtonTouchable}>
<Icon name="arrow-back-outline" size={28} color="#000" />
</TouchableOpacity>
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' }}>
<Text style={styles.title}>
New Meeting
</Text>
</View>
</View>
<View style={{ marginTop: 32 }}>
<MinimalTextInput title="When" placeholder="test" onFocus={_showPicker} />
</View>
</SafeAreaView>
</>
)
}
}