Persist state hook in React native - react-native

This is my first app in React Native.
I little to no experience in React but i have been using Vue.
I'm also new to state management.
I have started using hooks but the online tutorials show examples without hooks.
My question is, how do i persist the state that i have set with hooks?
I want to save the projects even when opening the app without internet.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import Geolocation from '#react-native-community/geolocation'
import { FlatList, ActivityIndicator, Text, View } from 'react-native'
axios.defaults.baseURL = 'http://www.json.test/api/'
export default () => {
const [loading, setLoading] = useState(true)
const [projects, setProjects] = useState([])
const [position, setPosition] = useState({
latitude: 0,
longitude: 0,
})
const getProjects = async () => {
// console.log()
const projects = await axios(
`projects/${position.latitude}/${position.longitude}`,
)
setProjects(projects.data)
setLoading(false)
}
useEffect(() => {
if (position.latitude != 0 && position.longitude != 0) {
getProjects()
}
}, [position])
useEffect(() => {
Geolocation.getCurrentPosition(
pos => {
setPosition({
latitude: pos.coords.latitude,
longitude: pos.coords.longitude,
})
},
error => console.log(error.message),
)
}, [])
if (loading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator
style={{
flex: 1,
padding: 20,
alignContent: 'center',
justifyContent: 'center',
}}
/>
</View>
)
}
return (
<View style={{ flex: 1, paddingTop: 80, paddingLeft: 50 }}>
<FlatList
data={projects}
renderItem={({ item }) => (
<View style={{ marginBottom: 20 }}>
<Text style={{ fontWeight: 'bold', fontSize: 16 }}>
{item.project_name}, {item.id}
</Text>
<Text>{item.project_description}</Text>
<Text style={{ fontStyle: 'italic' }}>
{Number(item.distance.toFixed(2))} Km
</Text>
</View>
)}
keyExtractor={(item, index) => index.toString()}
/>
{/* <Text>{position.latitude}</Text> */}
</View>
)
}
I searched the web but the tutorials only seem to focus on react and not on react native.
Thanks for your help!

You can use AsynStorage for persistence.
For AsyncStorage + hooks check it out react-native-hooks/async-storage library.
A simple code example show below:
import useAsyncStorage from '#rnhooks/async-storage';
function App() {
const [storageItem, updateStorageItem, clearStorageItem] = useAsyncStorage(
key,
);
return (
<View style={styles.container}>
<Text style={styles.type}>{`Storage Value: ${storageItem}`}</Text>
<Button
title="Update Item"
onPress={() => updateStorageItem('Test String')}
/>
<Button title="Clear Item" onPress={() => clearStorageItem()} />
</View>
);
}

Related

How to put the bottomsheet to the front of the screen?

In ReactNative, the bottomsheet is displayed overlaid on the fragment.
Is there a way to make the bottomsheet rise to the top of the screenenter image description here
The bottom sheet looks opaque as in the picture, so the bottom sheet cannot be touched Please help
The code below is a shortened version
enter image description here
enter image description here
import React, { FC , Component, useState, useEffect, Fragment,useCallback, useMemo, useRef } from "react"
import { FlatList, ViewStyle, StyleSheet, View, Platform, TextInput, TouchableOpacity} from "react-native"
import {
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetBackdrop,
} from '#gorhom/bottom-sheet';
const ROOT: ViewStyle = {
backgroundColor: DefaultTheme.colors.background,
flex: 1,
}
export const ChecklookupScreen: FC<StackScreenProps<NavigatorParamList, "checklookup">> = observer(function ChecklookupScreen() {
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handlePresentModalPress = useCallback((index: string) => {
LOG.info('handlePresentModalPress', index);
bottomSheetModalRef.current?.present();
}, []);
const handleSheetChanges = useCallback((index: number) => {
LOG.info
console.log('handleSheetChanges', index);
}, []);
const renderItem = ({ item, index }) => (
<TouchableOpacity
key={index + item.inspNo + item.spvsNo}
//style={listContainer}
onPress={throttle(() => {
onClickItem(item.inspNo,item.spvsNo);
})}
>
<View>
<Fragment>
</View>
<Button icon="magnify-expand"
mode="elevated"
style={styles.detailButton}
onPress={throttle(() => {
onClickItem(item.inspNo,item.spvsNo);
})}
// onPress={() => navigation.navigate("checkdetail")}
>
</Button>
</View>
</Fragment>
</View>
</TouchableOpacity>
);
const fetchChecklookups = async (offset: number) => {
LOG.debug('fetchChecklookups:' + offset);
setRefreshing(true);
await checklookupStore.getChecklookups(offset)
setRefreshing(false);
};
const onEndReached = () => {
if (checklookupStore?.checklookupsTotalRecord <= checklookups?.length) {
LOG.debug('onEndReached555555555');
} else {
setPage(page + 1)
fetchChecklookups(page + 1);
}
};
const [searchQuery, setSearchQuery] = React.useState('');
const onChangeSearch = query => setSearchQuery(query);
return (
<Screen preset="fixed" style={{ backgroundColor: colors.background, flex: 1, padding: 10,}}>
<View style={{ flex: 1,}}>
<View style={{ flex: 1, }}>
<Searchbar
placeholder="조회조건을 입력해주세요"
onChangeText={onChangeSearch}
value={searchQuery}
onPressIn={() => handlePresentModalPress('touch on')}
/>
<BottomSheetModalProvider>
<BottomSheetModal
backgroundStyle={{ backgroundColor: "gray" }}
style={styles.bottomSheet}
ref={bottomSheetModalRef}
index={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<View style={{ marginTop: 10, marginLeft: 50, marginRight: 50, flexDirection: "row"}}>
<View style={{ flex: 1, }}>
<Button
mode="outlined"
>소속을 입력하세요
</Button>
</View>
</View>
</BottomSheetModal>
</BottomSheetModalProvider>
</Screen>
)
})
You can try with portal, wrap you bottom sheet to from another package.

How I can call the key ID of flatlits into other function

In my app, i have a flat list in my app and there is a button I want to add not in the whole flatlist but some of the list components so that's why i need to call the key id of that specific component to the function.
Here is the code of flat list.
**
<FlatList
data={this.state.dataSource}
renderItem={this.renderItem}
// keyExtractor= {(item,index) => index}
keyExtractor={item => item.GameId.toString()}
ItemSeparatorComponent={this.renderSeprator}
refreshing = {this.state.refreshing}
onRefresh = {this.handleRefresh}
handlePress={item => item.GameId.toString()}
/>
**
This is the function where i want to call gameID
**
handlePress = () => {
if( this.GameId == 1){
this.setState({
btnvalue1: 'flex'});
console.log('ssss11');
} else {
this.setState({
btnvalue1: 'none'});
}
}
**
You can do like below
Code
import React, {useState} from 'react';
import {FlatList, View, Text} from 'react-native';
const myData = [
{
id: 1,
name: 'React',
},
{
id: 2,
name: 'Native',
},
];
export default function App() {
const [data, setData] = useState(myData);
const renderItem = ({item, index}) => {
return (
<View
style={{
height: 50,
width: '90%',
marginLeft: '5%',
flexDirection: 'row',
borderWidth: 1,
borderColor: 'black',
marginBottom: 10,
}}>
<Text>{item.name}</Text>
{item.name === 'Native' ? (
<View style={{height: 35, width: 35, backgroundColor: 'red'}}></View>
) : null}
</View>
);
};
return (
<View>
<FlatList
style={{marginTop: 50}}
data={data}
keyExtractor={(item, index) => String(index)}
renderItem={renderItem}
/>
</View>
);
}
Hope this helps !!!
Snack expo link

How do you get 1 specific value from a prop in expo?

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.

How to expand card onPress - React Native

I am trying to make a ticket app that allows for people to create tickets based on work that needs done. Right now, I need help with the expandable view for each ticket card. What I'm wanting is when a user presses on a specific card, it will expand the view and provide more details for only that card. What it is currently doing is expanding the view for every ticket card in the list. I'm new to React Native and trying my best, but nothing has worked so far.
Here is my parent which is called Home:
import React, {useState, useEffect} from 'react';
import {styles, Colors} from '../components/styles';
import { SafeAreaView } from 'react-native';
import Ticket from '../components/Ticket';
const data = [
{
name: 'Josh Groban',
subject: 'U-Joint',
desc: 'The bolt that is meant to hold the u-joint in place has the head broken off from it. See attached picture.',
assignee: 'John Doe',
assigneeConfirmedComplete: 'NA',
dateReported: 'Tue Mar 8, 2022',
vehicle: 'Truck 1',
media: '',
key: '1',
isShowing: false
},
// code removed for brevity
];
const Home = ({navigation}) => {
const [ticketList, setTicketList] = useState(data);
const getTickets = () => {
setTicketList(data);
}
useEffect(() => {
getTickets();
}, []);
return (
<SafeAreaView style={styles.HomeContainer}>
<Ticket
ticketList={ticketList}
setTicketList={setTicketList}
/>
</SafeAreaView>
)
};
export default Home;
And here is the main component that has all of the ticket card configurations:
import React, {useState, useEffect} from 'react';
import {Text, FlatList, View, SafeAreaView, Button, Image, TouchableOpacity } from 'react-native';
import {styles, Colors} from './styles';
import {Ionicons} from '#expo/vector-icons';
const Ticket = ({ticketList, setTicketList}) => {
const defaultImage = 'https://airbnb-clone-prexel-images.s3.amazonaws.com/genericAvatar.png';
const [isComplete, setIsComplete] = useState(false);
const [show, setShow] = useState(false);
const showContent = (data) => {
const isShowing = {...data, isShowing}
if (isShowing)
setShow(!show);
}
const completeTask = () => {
setIsComplete(!isComplete);
}
return (
<SafeAreaView style={{flex: 1}}>
<FlatList showsVerticalScrollIndicator={false}
data={ticketList}
renderItem={(data) => {
return (
<>
<TouchableOpacity key={data.item.key} onPress={() => showContent(data.item.isShowing = true)}>
<View style={styles.TicketCard}>
<Image
style={styles.TicketCardImage}
source={{uri: defaultImage}}
/>
<View style={styles.TicketCardInner}>
<Text style={styles.TicketCardName}>{data.item.vehicle}</Text>
<Text style={styles.TicketCardSubject}>
{data.item.subject}
</Text>
</View>
<TouchableOpacity>
<Ionicons
name='ellipsis-horizontal-circle'
color={Colors.brand}
size={50}
style={styles.TicketCardImage}
/>
</TouchableOpacity>
<TouchableOpacity onPress={completeTask}>
<Ionicons
name={isComplete ? 'checkbox-outline' : 'square-outline'}
color={Colors.brand}
size={50}
style={styles.TicketCardButton}
/>
</TouchableOpacity>
</View>
<View style={styles.TicketCardExpand}>
<Text>
{show &&
(<View style={{padding: 10}}>
<Text style={styles.TicketCardDesc}>
{data.item.desc}
</Text>
<Text style={{padding: 5}}>
Reported by: {data.item.name}
</Text>
<Text style={{padding: 5}}>
Reported: {data.item.dateReported}
</Text>
{isComplete && (
<Text style={{padding: 5}}>
Confirmed Completion: {data.item.assigneeConfirmedComplete}
</Text>
)}
</View>
)}
</Text>
</View>
</TouchableOpacity>
</>
)}}
/>
</SafeAreaView>
)
};
export default Ticket;
Lastly, here are the styles that i'm using:
import {StyleSheet } from "react-native";
import { backgroundColor } from "react-native/Libraries/Components/View/ReactNativeStyleAttributes";
// colors
export const Colors = {
bg: '#eee',
primary: '#fff',
secondary: '#e5e7eb',
tertiary: '#1f2937',
darkLight: '#9ca3f9',
brand: '#1d48f9',
green: '#10b981',
red: '#ff2222',
black: '#000',
dark: '#222',
darkFont: '#bbb',
gray: '#888'
}
export const styles = StyleSheet.create({
HomeContainer: {
flex: 1,
paddingBottom: 0,
backgroundColor: Colors.bg,
},
TicketCard : {
padding: 10,
justifyContent: 'space-between',
borderColor: Colors.red,
backgroundColor: Colors.primary,
marginTop: 15,
flexDirection: 'row',
},
TicketCardExpand: {
justifyContent: 'space-between',
backgroundColor: Colors.primary,
},
TicketCardImage: {
width: 60,
height: 60,
borderRadius: 30
},
TicketCardName:{
fontSize: 17,
fontWeight: 'bold'
},
TicketCardSubject: {
fontSize: 16,
paddingBottom: 5
},
TicketCardDesc: {
fontSize: 14,
flexWrap: 'wrap',
},
TicketCardInner: {
flexDirection: "column",
width: 100
},
TicketCardButton: {
height: 50,
}
});
Any help is greatly appreciated!
Create a Ticket component with its own useState.
const Ticket = (data) => {
const [isOpen, setIsOpen] = useState(false);
const handlePress = () => {
setIsOpen(!isOpen);
}
return (
<TouchableOpacity
onPress={handlePress}
>
// data.item if you use a list, otherwise just data
<YourBasicInformation data={data.item} />
{isOpen && <YourDetailedInformation data={data.item} />}
</TouchableOpacity>
)
}
Render one Ticket for every dataset you have.
<List
style={styles.list}
data={yourDataArray}
renderItem={Ticket}
/>
If you don't want to use a List, map will do the job.
{yourDataArray.map((data) => <Ticket data={data} />)}
instead of setting show to true or false you can set it to something unique to each card like
setShow(card.key or card.id or smth)
and then you can conditionally render details based on that like
{show == card.key && <CardDetails>}
or you can make an array to keep track of open cards
setShow([...show,card.id])
{show.includes(card.id) && <CardDetails>}
//to remove
setShow(show.filter((id)=>id!=card.id))

How can use useState() with Flatlist data?

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