React native button click render different components - react-native

I am using Expo React native for my app. I have created three buttons. When user will click the button it will fetch data and render it in one screen. Each button will button will fetch different api. Under the buttons there will be flatlist, where it will display the data. I can do with one button click display the data but I could not figure how to display other buttons api. I share my code in codesandbox. ps: Below code make this app get super slow
This is my code
import React from 'react';
import { Text, View, ScrollView } from 'react-native';
import styled from 'styled-components';
import PressableButton from './Button';
import axios from 'axios';
const api = "https://jsonplaceholder.typicode.com/users";
const anApi = "https://jsonplaceholder.typicode.com/photos"
const Data = ({ data }) => {
return (
<View style={{ flex: 1 }}>
<Text>{JSON.stringify(data, null, 4)}</Text>
</View>
)
}
const AnData = ({ andata }) => {
return (
<View style={{ flex: 1 }}>
<Text>{JSON.stringify(andata, null, 1)}</Text>
</View>
)
}
export default function App() {
const [data, setData] = React.useState([]);
const [anotherdata, setanotherData] = React.useState([]);
const updateState = async () => {
await axios(api)
.then((res) => {
setData(res.data);
})
.catch((err) => {
console.log("failed to catch", err);
});
};
const anoThereState = async () => {
await axios(anApi)
.then((res) => {
setanotherData(res);
})
.catch((err) => {
console.log("failed to catch", err);
});
};
return (
<React.Fragment>
<Container>
<PressableButton onPress={updateState} title='First button' bgColor='#4267B2' />
<PressableButton onPress={anoThereState} title='Second button' bgColor='lightblue' />
<PressableButton onPress={() => true} title='Third button' bgColor='#4267B2' />
</Container>
<Scroll>
{data && data === undefined ? <Text>loading</Text> : <Data data={data} />}
{anotherdata && anotherdata === undefined ? <Text>loading</Text> : <AnData andata={anotherdata} />}
</Scroll>
</React.Fragment>
);
}
const Container = styled.View`
flex-direction: row;
justify-content: center;
padding: 70px 0px 20px 0px;
`;
const Scroll = styled.ScrollView`
flex: 1;
`

Under the buttons there will be flatlist
You're not using FlatList, only showing response in text and the data for second button is huge that's why It's super slow.
Here are the changes I made, check if this is what you're looking for?
Also if you want one data to show at a time you can either use tabs or show/hide the data depending on selection like I've done in code.

Related

React Native BigList stopping and not calling 'onEndReached'

I'm very new to React Native and I'm having an issue using the BigList.
After four or five scrolls to the bottom of the list it just stops retrieving any more data, even though there is plenty more data in the DB. I can still scroll the list up and down but it's like the 'onEndReached' is no longer being fired for some reason.
I've done a Google search but nobody else seems to be having this problem so I assume the issue is with my code.
Here's my code:
import React, { useEffect, useState } from 'react';
import { View, Text, Image, Dimensions } from 'react-native';
import BigList from 'react-native-big-list';
const HomeScreen = ({ navigation }) => {
const BaseURL = "http://localhost:12646/"
const windowWidth = Dimensions.get('window').width;
const [homeImages, setHomeImages] = useState([]);
const [pageNumber, setOffset] = useState(1);
const [loading, setLoading] = useState(false);
useEffect(() => {
getData();
}, []);
const getData = () => {
if (!loading) {
setLoading(true);
var url = BaseURL + 'api/images/GetHomepageImages?PageNumber=' + pageNumber + '&PageSize=3';
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
setOffset(pageNumber + 1);
setHomeImages([...homeImages, ...responseJson]);
setLoading(false);
})
.catch((error) => {
console.error(error);
});
}
}
const renderItem = ({ item, index }) => {
return (
<View style={{ height: windowWidth }}>
<Image
style={{ width: windowWidth, height: windowWidth, }}
source={{ uri: BaseURL + "images/" + item.basePath + "/" + item.imageSquare }}
/>
</View>
);
}
return (
<View style={{flex: 1}}>
{
(homeImages != null) ?
<BigList
data={homeImages}
onEndReached={getData}
itemHeight={windowWidth}
renderItem={renderItem}
/>
: <Text>Loading</Text>
}
</View>
)
};
export default HomeScreen;
If anyone else is stuck on this I think I've fond the solution. Adding the following line as one of the options in the BigList appears to have fixed it: -
onEndReachedThreshold={1}
Since adding this it thankfully hasn't happened again

Expo BarcodeScanner works only the first time when I post data to my server on barcode scanned

Expo BarcodeScanner works only the first time when I post data to my server on barcode scanned
import React, { useState, useEffect } from "react";
import { Text, View, StyleSheet, Button } from "react-native";
import { BarCodeScanner } from "expo-barcode-scanner";
import { useSnapshot } from "valtio";
import phoneState from "../store/phoneState";
import { setqrText, setqrTextFlag } from "../store/phoneState";
export default function ScannerScreen({ navigation }) {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [text, setText] = useState("Not yet scanned");
const { qrText, qrTextFlag } = useSnapshot(phoneState);
useEffect(() => {
console.log("qrTexT: ", qrText);
}, [qrText]);
const askForCameraPermission = () => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === "granted");
})();
};
// Request Camera Permission
useEffect(() => {
askForCameraPermission();
}, []);
// What happens when we scan the bar code
const handleBarCodeScanned = ({ type, data }) => {
axios
.post(`http://${localIP}:5000/api/qr/scanQr`, { data })
.then((response) => {
const { message } = response.data;
})
.catch((e) => {
alert("ERR : ", e);
});
};
// Check permissions and return the screens
if (hasPermission === null) {
return (
<View style={styles.container}>
<Text>Requesting for camera permission</Text>
</View>
);
}
if (hasPermission === false) {
return (
<View style={styles.container}>
<Text style={{ margin: 10 }}>No access to camera</Text>
<Button
title={"Allow Camera"}
onPress={() => askForCameraPermission()}
/>
</View>
);
}
// Return the View
return (
<View style={styles.container}>
<View style={styles.barcodebox}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{ height: 400, width: 400 }}
/>
</View>
<Text style={styles.maintext}>{text}</Text>
{scanned && (
<Button
title={"Go Back"}
onPress={() => {
setScanned(false);
navigation.navigate("Home");
}}
color='tomato'
/>
)}
</View>
);
}
As you can see here, i want to send the scanned data to my server, in order to do that im using axios.post() after barcode scanned. At first try it works just fine, but afterwards it does not scan QR code. I tried to remove my axios.post() code and logged the scanned codes in the terminal, it is scanning all QR codes that i showed the camera as expected. How to change my code to prevent this happenning?
How about toggling some kind of waiting period while the data is actually getting sent to your server via the POST in a synchronous matter to stop the scan and then reactivate it right after?
I don't have the chance to test it on an actual device and replicate it to the precise bit but I hope you'll get the gist of it:
// What happens when we scan the bar code
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
(async () => {
try {
const response = await axios.post(`http://${localIP}:5000/api/qr/scanQr`, { data })
const { message } = response.data;
// automatically restoring scanned to keep the ball rolling on the onBarCodeScanned callback
setScanned(false);
} catch (e) {
alert("ERR : ", e);
}
})();
};
The Snack on the doc page also has such kind of behaviour and a warning notice is highlighted on the page itself:
Note: Passing undefined to the onBarCodeScanned prop will result in no
scanning. This can be used to effectively "pause" the scanner so that
it doesn't continually scan even after data has been retrieved.

React-Native Animated Accordion/ Drawer/ Drop-down/ Collapsible-card

I want to implement an animated accordion list/ drawer / drop-down menu / collapsible card.
The animation should be performant and look like this:
After a lot of searching, I could find many libraries. But I wanted to implement it without any library. Also, some tutorials showed how to build one, but they were not performant.
Finally, this is how I implemented it. The complete snack code is here: https://snack.expo.dev/#vipulchandra04/a85348
I am storing isOpen (whether the menu is open or closed) in a state. Then changing that state on button press. I am using the LayoutAnimation API in React-Native to animate the opening and closing of the list. LayoutAnimation runs the animation natively, thus it is performant.
const Accordion = ({ title, children }) => {
const [isOpen, setIsOpen] = useState(false);
const toggleOpen = () => {
setIsOpen(value => !value);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
}
return (
<>
<TouchableOpacity onPress={toggleOpen} activeOpacity={0.6}>
{title}
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</>
);
};
const styles = StyleSheet.create({
hidden: {
height: 0,
},
list: {
overflow: 'hidden'
},
});
With this, it will fix the Vipul's demo's error: if click accordion so fast, children's opacity descending to 0. and add animation for icon
import {
Animated,
LayoutAnimation,
Platform,
StyleProp,
StyleSheet,
UIManager,
View,
ViewStyle,
} from 'react-native';
import Ionicons from 'react-native-vector-icons/Ionicons;
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
const toggleAnimation = duration => {
return {
duration: duration,
update: {
property: LayoutAnimation.Properties.scaleXY,
type: LayoutAnimation.Types.easeInEaseOut,
},
delete: {
property: LayoutAnimation.Properties.opacity,
type: LayoutAnimation.Types.easeInEaseOut,
},
};
};
interface IAccordion {
title?: JSX.Element | JSX.Element[];
children?: JSX.Element | JSX.Element[];
style?: StyleProp<ViewStyle> | undefined;
}
const Accordion = ({title, children, style}: IAccordion) => {
const [isOpen, setIsOpen] = useState(false);
const animationController = useRef(new Animated.Value(0)).current;
const arrowTransform = animationController.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '90deg'],
});
const onToggle = () => {
setIsOpen(prevState => !prevState);
const duration = 300;
const config = {
duration: duration,
toValue: isOpen ? 0 : 1,
useNativeDriver: true,
};
Animated.timing(animationController, config).start();
LayoutAnimation.configureNext(toggleAnimation(duration));
};
return (
<View style={style ? style : styles.accordion}>
<TouchableOpacity onPress={onToggle} style={styles.heading}>
{title}
<Animated.View style={{transform: [{rotateZ: arrowTransform}]}}>
<Ionicons name={'chevron-forward-outline'} size={18} />
</Animated.View>
</TouchableOpacity>
<View style={[styles.list, !isOpen ? styles.hidden : undefined]}>
{children}
</View>
</View>
);
};
export default Accordion;
I had difficulty using the native API, so I go to third parties. The only thing I couldn't do was make the accordion size automatic.
import { useEffect } from 'react';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
Easing,
} from 'react-native-reanimated';
import styled from 'styled-components';
const Accordion = ({ children, open, height }) => {
const heightAnimation = useSharedValue(0);
useEffect(() => {
if (open === true) heightAnimation.value = height;
if (open === false) heightAnimation.value = 0;
}, [open]);
const animatedStyle = useAnimatedStyle(() => {
return {
height: withTiming(heightAnimation.value, {
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
};
});
return (
<>
<Main style={animatedStyle}>{children}</Main>
</>
);
};
const Main = styled(Animated.View)`
width: 100%;
align-items: center;
justify-content: center;
overflow: hidden;
`;
export default Accordion;
Using:
<Accordion height={height} open={open}>
{children}
</Accordion>
As asked here for an example of what I managed to do with it, I tried to get as much out of it as possible.
You can see a deploy here: https://snack.expo.dev/#francisco.ossian/accordion
Libs used, react-native-reanimated

How can I display a modal with info from a scanned QR code?

Tech stack: Expo, React-Native
Using: expo-barcode-scanner
I am trying to create an app that will scan a QR code and then display the info from the QR code on the screen, preferably in a modal so I can display an image.
I've created an app that will do that... except that it uses an alert function to show the text. How can I change it to a modal? When I try to change it to modal, or return a modal it doesn't work.
My code:
import React, { useCallback, useEffect, useState } from 'react';
import { StyleSheet, Text, View, Image, Button, Modal } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import {createCustomerInformation2} from './src/graphql/mutations'
import logo from './assets/icon.png';
import Scanner from './components/QRScanner';
import custModal from './components/custInfo'
export default function App() {
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
// const [modalVisible, setModalVisible] = useState(false);
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
var newData = JSON.parse(data)
// return(
// <View>
// <Modal>
// <View>
// <Text>Test</Text>
// </View>
// </Modal>
// </View>
// )
alert(`
Customer: ${newData.name}
Email: ${newData.email}
Phone: ${newData.phone}
Favorite Drink: ${newData.favoriteDrink}
`);
// createCustomerInformation2(newData)();
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View id="view" style={styles.container}>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
// If you want to use the front facing or rear facing, include type={'front'} or put 'back'
/>
{scanned && <Button title={'Tap to Scan Again'} onPress={() => setScanned(false)} />}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
},
});
Instead of returning that JSX in the onBarCodeScanned handler, you'll need to include it in the returned JSX of your App. You'll want to conditionally hide the modal when no value is present. You'll then populate it by assigning the scanned value to a state variable in the event handler and displaying that value in the modal.

Application wide Modal in React Native

I'm currently using react native modal and it serves the purpose of showing modals.
My problem currently is that I want to show the modal application wide. For example when a push notification received I want to invoke the modal regardless of which screen user is in. The current design of the modals bind it to a single screen.
How can this be overcome?
first of all make a context of your modal
const BottomModal = React.createContext();
then provide your modal using reactcontext provider
export const BottomModalProvider = ({children}) => {
const panelRef = useRef();
const _show = useCallback((data, type) => {
panelRef.current.show();
}, []);
const _hide = useCallback(() => {
panelRef.current.hide();
}, []);
const value = useMemo(() => {
return {
_show,
_hide,
};
}, [_hide, _show]);
return (
<BottomPanelContext.Provider value={value}>
{children}
<BottomPanel fixed ref={panelRef} />
</BottomPanelContext.Provider>
);
};
here is code for bottom panel
function BottomPanel(props, ref) {
const {fixed} = props;
const [visible, setVisibility] = useState(false);
const _hide = () => {
!fixed && hideModal();
};
const hideModal = () => {
setVisibility(false);
};
useImperativeHandle(ref, () => ({
show: () => {
setVisibility(true);
},
hide: () => {
hideModal();
},
}));
return (
<Modal
// swipeDirection={["down"]}
hideModalContentWhileAnimating
isVisible={visible}
avoidKeyboard={true}
swipeThreshold={100}
onSwipeComplete={() => _hide()}
onBackButtonPress={() => _hide()}
useNativeDriver={true}
style={{
justifyContent: 'flex-end',
margin: 0,
}}>
<Container style={[{flex: 0.9}]}>
{!fixed ? (
<View style={{flexDirection: 'row', justifyContent: 'flex-end'}}>
<Button
style={{marginBottom: 10}}
color={'white'}
onPress={() => setVisibility(false)}>
OK
</Button>
</View>
) : null}
{props.renderContent && props.renderContent()}
</Container>
</Modal>
);
}
BottomPanel = forwardRef(BottomPanel);
export default BottomPanel;
then wrap your app using the provider
...
<BottomModalProvider>
<NavigationContainer screenProps={screenProps} theme={theme} />
</BottomModalProvider>
...
lastly how to show or hide modal
provide a custom hook
const useBottomPanel = props => {
return useContext(BottomPanelContext);
};
use it anywhere in app like
const {_show, _hide} = useBottomModal();
//....
openModal=()=> {
_show();
}
//...
If you are not using hooks or using class components
you can easily convert hooks with class context
https://reactjs.org/docs/context.html#reactcreatecontext
this way you can achieve only showing the modal from within components
another way is store the panel reference globally anywhere and use that reference to show hide from non-component files like redux or notification cases.