I have been trying to achieve hiding (or slide up) my react native startup app top bar after 3 seconds, then have a button to press to show the top bar, but could not. I've searched online how to do it, but have not seen good solution for it. Please I need help here, the below is snippet code of what I tried doing but it did not work.
<HeaderHomeComponent /> is the top header I created and imported it in my Home screen
const Home = () => {
const camera = useRef();
const [isRecording, setIsRecording] = useState(false);
const [flashMode, setFlashMode] = useState(RNCamera.Constants.FlashMode.off);
const [cameraType, setCameraType] = useState(RNCamera.Constants.Type.front);
const [showHeader, setShowHeader] = useState(true);
const onRecord = async () => {
if (isRecording) {
camera.current.stopRecording();
} else {
setTimeout(() => setIsRecording && camera.current.stopRecording(), 23*1000);
const data = await camera.current.recordAsync();
}
};
setTimeout(()=> setShowHeader(false),3000);
const DisplayHeader = () => {
if(showHeader == true) {
return <HeaderHomeComponent />
} else {
return null;
}
}
// useEffect(() => {
// DisplayHeader();
// }, [])
return (
<View style={styles.container}>
<RNCamera
ref={camera}
type={cameraType}
flashMode={flashMode}
onRecordingStart={() => setIsRecording(true)}
onRecordingEnd={() => setIsRecording(false)}
style={styles.preview}
/>
<TouchableOpacity
style={styles.showHeaderButton}
onPress={() => {
setShowHeader(!showHeader);
}}>
<Button title='Show' />
</TouchableOpacity>
<HeaderHomeComponent />
You were really close.
This is how it should be done:
useEffect(() => {
setTimeout(toggleHeader,3000);
}, []);
const toggleHeader = () => setShowHeader(prev => !prev);
Then inside the "return":
{showHeader && <HeaderHomeComponent />}
As simple as that.
This should help you get started in the right direction, you can use animation based on your preference to this code.
import React, {useState, useEffect} from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import Constants from 'expo-constants';
export default function App()
{
const [showStatusbar, setShowStatusbar] = useState(true)
useEffect(() =>
{
setTimeout(() =>
{
setShowStatusbar(false)
}, 3000)
}, [])
return (
<View style={styles.container}>
{
showStatusbar
? <View style = {styles.statusBar}>
<Text>Status Bar</Text>
</View>
: null
}
<Button title = "Show Status bar" onPress = {() => setShowStatusbar(true)}/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ecf0f1',
},
statusBar:
{
height: 50,
backgroundColor:'lightblue',
justifyContent:'center',
alignItems:'center'
}
});
Instead of setTimeout use Animation.
or
Check this lib : https://www.npmjs.com/package/react-native-animated-hide-view
or
check below answers which might help you
Hide and Show View With Animation in React Native v0.46.
Related
I have added two onPress event so that I can go to the next page and show the ads on click. When I press the button the page navigates to another page but the ads is not showing. I have not get any errors, what is the issue here
import React, { useEffect, useState } from 'react';
import { Button, TouchableOpacity } from 'react-native';
import { InterstitialAd, AdEventType, TestIds } from 'react-native-google-mobile-ads';
const adUnitId = __DEV__ ? TestIds.INTERSTITIAL : 'ca-app-pub-3940256099942544/1033173712';
const interstitial = InterstitialAd.createForAdRequest(adUnitId, {
requestNonPersonalizedAdsOnly: true,
keywords: ['fashion', 'clothing'],
});
const Testing = ({ navigation }) =>{
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const unsubscribe = interstitial.addAdEventListener(AdEventType.LOADED, () => {
setLoaded(true);
});
// Start loading the interstitial straight away
interstitial.load();
// Unsubscribe from events on unmount
return unsubscribe;
}, []);
// No advert ready to show yet
if (!loaded) {
return null;
}
return (
<View>
<TouchableOpacity onPress={() => {interstitial.show();}}>
<Button onPress = {() => navigation.navigate('FirstPage')} title='Next Screen'></Button>
</TouchableOpacity>
<Text>Hello World Testing</Text>
</View>
);}
export default Testing
try with hooks - its working for me
ref - https://docs.page/invertase/react-native-google-mobile-ads/displaying-ads-hook
import { useInterstitialAd, TestIds } from 'react-native-google-mobile-ads';
export default function App({ navigation }) {
const { isLoaded, isClosed, load, show } = useInterstitialAd(TestIds.Interstitial, {
requestNonPersonalizedAdsOnly: true,
});
useEffect(() => {
// Start loading the interstitial straight away
load();
}, [load]);
useEffect(() => {
if (isClosed) {
// Action after the ad is closed
navigation.navigate('NextScreen');
}
}, [isClosed, navigation]);
return (
<View>
<Button
title="Navigate to next screen"
onPress={() => {
if (isLoaded) {
show();
} else {
// No advert ready to show yet
navigation.navigate('NextScreen');
}
}}
/>
</View>
);
}
So essentially, I want to play the lottie animation everytime it is tapped. Here is my UI code for the lottie animation:
<Pressable onPress={playGame}>
<LottieView
ref={loseAnimationRef}
style={styles.egg}
source={Lost}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
</Pressable>
Here is my state code for the lottie animation:
const loseAnimationRef = useRef(null);
const playGame = async () => {
await mainGameLogicUC();
playAnimation()
};
const playAnimation = () => {
loseAnimationRef.current.play()
}
const resetAnimation = () => {
loseAnimationRef.current.reset()
}
On the first tap, the animation play perfefctly fine. But on all other taps, the animation won't play. I tried pausing the animation in the onAnimationFinish and then resuming it, but that also didn't work. Am I missing something?
EDIT
I got rid of the resetAnimation() in the onAnimationFinish and that solved the initial problem. But the thing is, I want the animation to be reset to the beginning every time. Why does it break when I reset the animation?
Have you tried something like this?
const animationRef = useRef<AnimatedLottieView>()
const isAnimating = useRef<boolean>(false)
const onAnimationPress = () => {
if (!isAnimating.current) {
isAnimating.current = true
animationRef.current.play()
}
}
<TouchableWithoutFeedback onPress={onAnimationPress}>
<AnimatedLottieView
source={source}
ref={animationRef}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
isAnimating.current = false
animationRef.current.reset()
}}
/>
</TouchableWithoutFeedback>
After coming back to this problem a few days later, I found the solution
Playing the lottie animation seems to be considered a side effect, therefore, editing the references to the animations should be done in a useEffect hook
The solution that worked for me:
(again, in this code, I want the animation to reset to the beginning before the user taps the screen screen again.
state code
const isMounted = useRef(false);
const [isWonAnimationShowing, setIsWonAnimationShowing] = useState(false);
const [isAnimationPlaying, setIsAnimationPlaying] = useState(false);
const loseAnimationRef = useRef(null);
const winAnimationRef = useRef(null);
useEffect(() => {
if (isMounted.current) {
if (isAnimationPlaying) {
_playAnimation();
} else {
_resetAnimation();
}
} else {
isMounted.current = true;
}
}, [isAnimationPlaying]);
const playAnimation = () => {
setIsAnimationPlaying(true);
};
const _playAnimation = () => {
if (isWonAnimationShowing) {
winAnimationRef.current.play();
} else {
loseAnimationRef.current.play();
}
};
const resetAnimation = () => {
setIsAnimationPlaying(false);
};
const _resetAnimation = () => {
if (isWonAnimationShowing) {
winAnimationRef.current.reset();
} else {
loseAnimationRef.current.reset();
}
};
UI code
<View style={styles.body}>
<Pressable disabled={isAnimationPlaying} onPress={playGame}>
{isWonAnimationShowing ? (
<LottieView
ref={winAnimationRef}
style={styles.egg}
source={Won}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
) : (
<LottieView
ref={loseAnimationRef}
style={styles.egg}
source={Lost}
autoPlay={false}
loop={false}
onAnimationFinish={() => {
resetAnimation();
}}
/>
)}
</Pressable>
</View>
I have a custom component where I want to prevent the useEffect to fire every time the component is rendering.
The main idea is to get the font-family name from the API and then pass it to the style value, so I want to get the font family just once - not every time the component renders in other screens.
Here's what I tried, but it doesn't work as expected, it's not updating the state after getting the value from API (getValue() not called).
import React, {useCallback, useEffect, useRef} from 'react';
import {useState} from 'react';
import {Text, StyleSheet, Platform} from 'react-native';
import {COLORS} from '../../common';
const AppText = ({children, style, ...rest}) => {
const isMounted = useRef(false);
const [fontFamily, setFontFamily] = useState('Helvetica-Bold');
const getValue = useCallback(() => {
// mock API
setTimeout(() => {
console.log('AppText: get font family name from API!!');
setFontFamily('HelveticaNeue');
}, 200);
}, []);
useEffect(() => {
if (isMounted.current) {
getValue();
} else {
isMounted.current = true;
return;
}
}, [getValue]);
return (
<Text
style={[
styles.text,
style,
{
fontFamily: fontFamily,
},
]}
{...rest}>
{children}
</Text>
);
};
export {AppText};
using:
Home/About/etc // other screens
const Home = () => {
return(
<View>
<AppText> Hey from home </AppText>
</View>
);
}
You can use a context to propergate the value down into multiple components (or use props) and just fetch it once higher up in the tree.
//App.js
import {useEffect, createContext, useState} from "react";
export const FontFamilyContext = createContext("DefaultFont");
const App = () =>{
const [font,setFont] = useState();
useEffect( () =>{
setFont(loadFont());
},[]);
return (
<FontFamilyContext.Provider value={font} >
<Screen />
<ScreenViaProp fontFamily={font} />
</FontFamilyContext.Provider>
);
}
export default App;
//Screen.jsx
//Advantage The font family can be used in nested components deep down
import { useContext } from "react";
import {FontContext} from "./App";
const Screen = () =>{
const fontFamily = useContext(FontFamilyContext);
return (
<div style={{fontFamily: fontFamily}}>
</div>
)
}
// ScreenViaProp .jsx Easier and no context is required
const ScreenViaProp = ({fontFamily}) =>{
return (
<div style={{fontFamily: fontFamily}}>
</div>
)
}
const DEFAULT_FONT_FAMILY = 'Helvetica-Bold';
const AppText = ({children, style, ...rest}) => {
const [fontFamily, setFontFamily] = useState(DEFAULT_FONT_FAMILY);
useEffect(() => {
// mock API
setTimeout(() => {
console.log('AppText: get font family name from API!!');
const FETCHED_FONT_FAMILY = 'HelveticaNeue';
if (FETCHED_FONT_FAMILY !== fontFamily) setFontFamily(FETCHED_FONT_FAMILY);
}, 200);
}, []);
return (
<Text
style={[
styles.text,
style,
{
fontFamily: fontFamily,
},
]}
{...rest}>
{children}
</Text>
);
};
You can also use a custom hook to load the font value only once in you app :
customHooks.js
let fontFromApi = null;
const fontP = new Promise(resolve => {
setTimeout(() => {
console.log('get font family name from API!!');
fontFromApi = 'HelveticaNeue';
resolve(fontFromApi);
}, 200);
});
export function useFont() {
const [font, setFont] = useState(fontFromApi || "Helvetica-Bold");
if (fontFromApi === null) {
fontP.then(v => setFont(v));
}
return font;
}
I declared a custom hook called useFont returning the font from the API. If the font is not loaded yet, it will return the fallback value Helvetica-Bold.
AppText.jsx
const { useFont } from './customHooks'
const AppText = ({children, style, ...rest}) => {
const fontFamily = useFont();
return (
<Text
style={[
styles.text,
style,
{ fontFamily }
]}
{...rest}>
{children}
</Text>
);
};
I am trying to build a simple stopwatch app in react-native. I'm using AsyncStorage to store the time recorded data into local storage, along with that I would like to display a table that shows all the recorded times. The core idea is that when a person presses and holds a LottieView animation, it will start a timer, when they press out, the timer stops, records in AsyncStorage and then updates the table.
After 10 elements, my FlatList (inside TimeTable.jsx) becomes extremely slow and I am not sure why. The component that is causing this error is I believe TimeTable.jsx but I am not quite sure why.
src/components/Timer/TimeTable.jsx
import React, {useState, useEffect} from 'react'
import { StyleSheet, FlatList } from "react-native";
import { Divider, List, ListItem } from '#ui-kitten/components'
import AsyncStorage from '#react-native-async-storage/async-storage';
const getRecordedEventsTable = async (dbKey) => {
try {
let currentDataArray = await AsyncStorage.getItem(dbKey);
return currentDataArray ? JSON.parse(currentDataArray) : [];
} catch (err) {
console.log(err);
}
};
const renderItem = ({ item, index }) => (
<ListItem
title={`${item.timeRecorded / 1000} ${index + 1}`}
description={`${new Date(item.timestamp)} ${index + 1}`}
/>
)
export const TimeTable = ({storageKey, timerOn}) => {
const [timeArr, setTimeArr] = useState([]);
useEffect(() => {
getRecordedEventsTable(storageKey).then((res) => {
setTimeArr(res)
})
}, [timerOn])
return (
<FlatList
style={styles.container}
data={timeArr}
ItemSeparatorComponent={Divider}
renderItem={renderItem}
keyExtractor={item => item.timestamp.toString()}
/>
);
};
const styles = StyleSheet.create({
container: {
maxHeight: 200,
},
});
src/components/Timer/Timer.jsx
import React, {useState, useEffect, useRef} from 'react'
import {
View,
StyleSheet,
Pressable,
} from 'react-native';
import {Layout, Button, Text} from '#ui-kitten/components';
import LottieView from 'lottie-react-native'
import AsyncStorage from '#react-native-async-storage/async-storage';
import {TimeTable} from './TimeTable'
const STORAGE_KEY = 'dataArray'
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#E8EDFF"
},
seconds: {
fontSize: 40,
paddingBottom: 50,
}
})
const getRecordedEventsTable = async () => {
try {
let currentDataArray = await AsyncStorage.getItem(STORAGE_KEY)
return currentDataArray ? JSON.parse(currentDataArray) : []
} catch (err) {
console.log(err)
}
}
const addToRecordedEventsTable = async (item) => {
try {
let dataArray = await getRecordedEventsTable()
dataArray.push(item)
await AsyncStorage.setItem(
STORAGE_KEY,
JSON.stringify(dataArray)
)
} catch (err) {
console.log(err)
}
}
// ...
const Timer = () => {
const [isTimerOn, setTimerOn] = useState(false)
const [runningTime, setRunningTime] = useState(0)
const animation = useRef(null);
const handleOnPressOut = () => {
setTimerOn(false)
addToRecordedEventsTable({
timestamp: Date.now(),
timeRecorded: runningTime
})
setRunningTime(0)
}
useEffect(() => {
let timer = null
if(isTimerOn) {
animation.current.play()
const startTime = Date.now() - runningTime
timer = setInterval(() => {
setRunningTime(Date.now() - startTime)
})
} else if(!isTimerOn) {
animation.current.reset()
clearInterval(timer)
}
return () => clearInterval(timer)
}, [isTimerOn])
return (
<View>
<Pressable onPressIn={() => setTimerOn(true)} onPressOut={handleOnPressOut}>
<LottieView ref={animation} style={{width: 300, height: 300}} source={require('../../../assets/record.json')} speed={1.5}/>
</Pressable>
<Text style={styles.seconds}>{runningTime/1000}</Text>
<TimeTable storageKey={STORAGE_KEY} timerOn={isTimerOn} />
<Button onPress={resetAsyncStorage}>Reset Async</Button>
</View>
)
}
export default Timer
Any help, appreciated. Thanks.
EDIT:
Received the following warning in console:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
"contentLength": 1362.5,
"dt": 25161,
"prevDt": 368776,
EDIT:
In Timer.jsx, I have a Text View in the render function as follows:
<Text style={styles.seconds}>{runningTime/1000}</Text>, this part is supposed to show the stopwatch value and update with the timer.
As the FlatList gets bigger, this is the part that becomes extremely laggy.
My suspicion is that as this is trying to re-render constantly, the children component TimeTable.jsx is also re-rendering constantly?
Looks to me like you have a loop here:
useEffect(() => {
getRecordedEventsTable(storageKey).then((res) => {
setTimeArr(res)
})
}, [timeArr, timerOn])
useEffect will get called every time timeArr is updated. Then, inside you call your async getRecordedEventsTable, and every time that finishes, it'll call setTimeArr, which will set timeArr, triggering the sequence to start again.
For optimizing the FlatList you can use different parameters that are available. You can read this https://reactnative.dev/docs/optimizing-flatlist-configuration.
Also you might consider using useCallback hook for renderItems function.
I would recommend reading this https://medium.com/swlh/how-to-use-flatlist-with-hooks-in-react-native-and-some-optimization-configs-7bf4d02c59a0
I was able to solve this problem.
The main culprit for the slowness was that in the parent component Timer.jsx because the timerOn props is changing everytime the user presses the button, the whole children component is trying to re-render and that AsyncStorage call is being called everytime. This is the reason that the {runningTime/1000} is rendering very slowly. Because everytime the timerOn component changes all child components have been queued to re-render.
The solution for this was to render the Table component from a parent of Timer and not inside the Timer component and maintain a state in Timer which is passed back to the parent and then passed to the Table component.
This is what my parent component looks like now:
const [timerStateChanged, setTimerStateChanged] = useState(false);
return (
<View style={styles.container}>
<Timer setTimerStateChanged={setTimerStateChanged} />
<View
style={{
borderBottomColor: "grey",
borderBottomWidth: 1,
}}
/>
<TimeTable timerOn={timerStateChanged} />
</View>
);
};
A better way would be to use something like React context or Redux.
Thanks for all the help.
How to take full page screenshot of webview in react native? Already tried "react-native-view-shot " Link but it only takes screenshot of the visible area.
Someone please help.
Thanks
For future readers:
Below is the final script after many attempts, which takes full page screenshot from Webview. I have used ProgressWebView instead of Webview. You can use Webview if you want.
This code works in functional components.
Note: When the page is fully loaded then click on the Take Screenshot button
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import { captureRef } from "react-native-view-shot";
import ProgressWebView from "react-native-progress-webview";
import json5 from 'json5'
import { Dimensions } from 'react-native';
const Report = () => {
const [componentHeight, setComponentHeight] = useState(0)
const [globalComponentHeight, setGlobalComponentHeight] = useState(0)
const [componentHeightFlex, setComponentHeightFlex] = useState(1)
let url = 'https://stackoverflow.com/questions/63708244/webview-full-page-screenshot-in-react-native'
let webview = null;
let count = 0
const injectJS = _ => {
const script = `
let method${count} = _ => {
let documentHeight = document.body.scrollHeight
let data = {componentHeight: documentHeight}
window.ReactNativeWebView.postMessage(JSON.stringify(data))
}
method${count}()`
webview.injectJavaScript(script)
count++
}
const takeScreenshot = _ => {
console.log(globalComponentHeight)
const {height} = Dimensions.get("window")
console.log(height)
if(globalComponentHeight <= height) setComponentHeight(height)
else setComponentHeight(globalComponentHeight)
setComponentHeightFlex(null)
setTimeout(_ => {
captureRef(webview, {
format: "png",
quality: 0.9,
result: "base64"
}).then(
_screenshot => {
console.log(_screenshot)
//First save your screenshot from _screenshot(base64 string). You can send base64 string to your server and save
//Then make the component default as below
setComponentHeight(0)
setComponentHeightFlex(1)
},
error => console.error("Oops, screenshot failed", error)
);
}, 100)
}
return (
<View style={{ marginTop: 40, flex: 1, display: 'flex' }}>
<Button mode='contained' onPress={takeScreenshot} title="Take Screenshot"/>
<View
style={{
height: componentHeight,
flex: componentHeightFlex
}}
>
<ProgressWebView
ref={ref => {
if (ref != null)
webview = ref
}}
bounces={false}
style={{ position: 'relative' }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={true}
source={{ uri: url }}
startInLoadingState={true}
onLoad={e => injectJS()}
onMessage={e => {
let data = json5.parse(e.nativeEvent.data)
// console.log(data)
setGlobalComponentHeight(parseInt(data.componentHeight))
}}
></ProgressWebView>
</View>
</View>
);
}
export default Report