How do I take a screenshot of View in React Native? - react-native

I want to share screenshot of particular View Component instead of whole screen.
Any one help me out with this.
Take a look a picture. Want screenshot of Red mark area which is within View Component.

You can use library named react-native-view-shot
You just have to give wrap your View inside ViewShot, take a reference of that and call capture()
Here is example of code taken from that library
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>
);
}
}

Here is a working example example of code using react-native-view-shot with hooks
import React, { useState, useRef, useEffect } from "react";
import { View, Image, ScrollView, TouchableOpacity } from "react-native";
import ViewShot from "react-native-view-shot";
var RNFS = require("react-native-fs");
import Share from "react-native-share";
const TransactionReceipt = () => {
const viewShotRef = useRef(null);
const [isSharingView, setSharingView] = useState(false);
useEffect(() => {
if (isSharingView) {
const shareScreenshot = async () => {
try {
const uri = await viewShotRef.current.capture();
const res = await RNFS.readFile(uri, "base64");
const urlString = `data:image/jpeg;base64,${res}`;
const info = '...';
const filename = '...';
const options = {
title: info,
message: info,
url: urlString,
type: "image/jpeg",
filename: filename,
subject: info,
};
await Share.open(options);
setSharingView(false);
} catch (error) {
setSharingView(false);
console.log("shareScreenshot error:", error);
}
};
shareScreenshot();
}
}, [isSharingView]);
return (
<ViewShot ref={viewShotRef} options={{ format: "jpg", quality: 0.9 }}>
<View>
{!isSharingView && (
<TouchableOpacity onPress={() => setSharingView(true)}>
<Image source={Images.shareIcon} />
</TouchableOpacity>
)}
<ScrollView />
</View>
</ViewShot>);
}

Related

How to download a pdf file in a react-native iOS webview?

Just developing a simple react-native app using expo and react-native-webview library.
The problem is that when users try to download an invoice in pdf format, iOS shows the preview and it's not possible to go back to the app.
Here attached the main app screen component:
import React, { useState } from "react";
import { ActivityIndicator, Share, StyleSheet } from "react-native";
import * as FileSystem from 'expo-file-system';
const { downloadAsync, documentDirectory } = FileSystem;
import { SafeAreaView } from 'react-native-safe-area-context';
import { WebView } from 'react-native-webview';
const HomeScreen = ({ navigation }) => {
const [loading, setLoading] = useState(true);
let downloadDocument = async (downloadUrl) => {
alert('downloadUrl 2: ', downloadUrl);
let fileURI = await downloadAsync(
downloadUrl,
`${documentDirectory}/invoice.pdf`,
{}
);
await onShare(fileURI.uri);
}
const onShare = async (url) => {
try {
return Share.share({
message: 'Select storage location',
url: url
});
} catch (error) {
alert('error: ', error);
return error;
}
};
return (
<SafeAreaView style={styles.container}>
<WebView
source={{ uri: '<url>' }}
onError={() =>
navigation.navigate('Error')
}
setSupportMultipleWindows={false}
startInLoadingState={true}
renderLoading={() =>
<ActivityIndicator
style={styles.spinner}
size='large'
color='#0098D4'
/>
}
domStorageEnabled={true}
// iOS
onFileDownload={({ nativeEvent: { downloadUrl } }) => {
alert('downloadUrl: ', downloadUrl);
downloadDocument(downloadUrl);
}}
/>
</SafeAreaView>
);
}
We added some alerts, but the're never fired.
In the html code, there is an tag with href property pointing to the file's url and the download option set.
Any solution?

How to change ref.current.children with useRef?

I wanted to change the content of the ref.current.children with ref.
Just like this:
import React, { useRef, useEffect } from 'react';
const MyApp = () => {
const aRef = useRef();
useEffect(()=> {
const node =aRef.current;
setTimeout(() => {
// Here I wanted to change the content of <Text> to 'Edited' but nothing happened.
node.children = React.Children.map(node.children, (child) => {
React.cloneElement(child, {}, 'Edited')
})
}, 1000)
}, [])
return (
<View ref={aRef}>
<Text>to be edited 1</Text>
<Text>to be edited 2</Text>
</View>
);
}
What I got:
to be edited 1
to be edited 2
What I wanted:
Edited
Edited
Is there a way to get what I wanted?

ref.current null is not an object

I created a WebView component. And I need to send a script there.
To do this, I created a ref for a webview component (webViewRef).
The problem is that when the ref is FIRST triggered, the ref is empty (webViewRef.current null is not an object) and the injection does not work. All subsequent ones work fine.
import { useEffect, useRef } from 'react'; import * as React from 'react';
import { WebView } from 'react-native-webview';
import { useKeyboardStatus } from './useKeyboardHook';
export function Screen() {
const keyboardIsOpen = useKeyboardStatus();
let webViewRef = useRef<WebView>();
useEffect(() => {
scrollToBottom();
}, [keyboardIsOpen]);
function scrollToBottom(): void {
if (webViewRef.current !== null) {
webViewRef.current.injectJavaScript('some JavaScript');
}
};
return (
<WebView
ref={webViewRef}
source={{ uri: URL }}
javaScriptEnabled={true}
/>
);
}
how can i fix this to work the first time?
You get the WebView ref after the useEffect run.
The first option, is to call scrollToBottom when you get the ref:
export function Screen() {
const keyboardIsOpen = useKeyboardStatus();
let webViewRef = useRef<WebView>();
useEffect(() => {
scrollToBottom();
}, [keyboardIsOpen]);
function scrollToBottom() {
webViewRef.current?.injectJavaScript('some JavaScript');
}
const handleWebViewRef = (ref: WebView) => {
webViewRef.current = ref;
scrollToBottom();
};
return (
<WebView ref={handleWebViewRef}
source={{uri: URL}}
javaScriptEnabled={true}/>
);
}
A nicer option in my opinion, will be to use useState instead useRef and add it to the dependencies array of the useEffect hook:
export function Screen() {
const keyboardIsOpen = useKeyboardStatus();
const [webViewRef, setWebViewRef] = useState<WebView>();
useEffect(() => {
scrollToBottom();
}, [keyboardIsOpen, webViewRef]);
function scrollToBottom() {
webViewRef?.injectJavaScript('some JavaScript');
}
return (
<WebView ref={ref => setWebViewRef(ref)}
source={{uri: URL}}
javaScriptEnabled={true}/>
);
}
The problem is the useEffect hook is not getting the updated reference of the webview jus add webViewRef in the dependencies of useEffect
Snack Link: https://snack.expo.io/#ashwith00/humiliated-marshmallows
import { useEffect, useRef } from 'react'; import * as React from 'react';
import { WebView } from 'react-native-webview';
import { useKeyboardStatus } from './useKeyboardHook';
export function Screen() {
const keyboardIsOpen = useKeyboardStatus();
const webViewRef = useRef();
useEffect(() => {
scrollToBottom();
}, [keyboardIsOpen, webViewRef]);
function scrollToBottom(): void {
webViewRef.current?.injectJavaScript('some JavaScript');
};
return (
<WebView
ref={webViewRef}
source={{ uri: URL }}
javaScriptEnabled={true}
/>
);
}

Screen State not Updating from AsyncStorage when going back

I'm building a React Native app.
My app has 5 Screens: Home (initialRouteName), DeckPage, QuestionPage, NewCardPage, NewDeckPage. (in this order)
I'm using Redux for state management. The state is updating from AsyncStorage.
The component that does the fetching is the class component "Home" by dispatching the "fetching" function in componentDidMount.
Component NewCardPage, NewDeckPAge are also updating the state with new content by dispatching the same fetching function as the Home when a button is pressed.
My problem appears when I want to delete a Deck component from inside DeckPage parent component. The function that does this job has this functionality: after removing the item from AsyncStorage, updates the STATE, and moves back to Screen HOME. The issue is that when I go back to HOME component the state doesn't update with the latest info from AsyncStorage.
This is not the case when I'm doing the same operation in the other 2 components NewCardPage, NewDeckPage.
I'll paste the code below:
import React, { Component } from "react";
import { connect } from "react-redux";
import { View, Text, StyleSheet, FlatList } from "react-native";
import Header from "../components/Header";
import AddDeckButton from "../components/AddDeckButton";
import DeckInList from "../components/DeckInList";
import { receiveItemsAction } from "../redux/actions";
class Home extends Component {
componentDidMount() {
this.props.getAsyncStorageContent();
}
renderItem = ({ item }) => {
return <DeckInList {...item} />;
};
render() {
const { items } = this.props;
// console.log(items);
const deckNumber = Object.keys(items).length;
return (
<View style={styles.container}>
<Header />
<View style={styles.decksInfoContainer}>
<View style={styles.deckNumber}>
<View style={{ marginRight: 50 }}>
<Text style={styles.deckNumberText}>{deckNumber} Decks</Text>
</View>
<AddDeckButton />
</View>
<View style={{ flex: 0.9 }}>
<FlatList
data={Object.values(items)}
renderItem={this.renderItem}
keyExtractor={(item) => item.title}
/>
</View>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getAsyncStorageContent: () => dispatch(receiveItemsAction()),
};
};
-----------DECKPAGE COMPONENT------------
import React from "react";
import { View, StyleSheet } from "react-native";
import Deck from "../components/Deck";
import { useSelector, useDispatch } from "react-redux";
import { removeItemAction, receiveItemsAction } from "../redux/actions";
import AsyncStorage from "#react-native-community/async-storage";
const DeckPage = ({ route, navigation }) => {
const { title, date } = route.params;
const questions = useSelector((state) => state.items[title].questions);
const state = useSelector((state) => state.items);
const dispatch = useDispatch();
// const navigation = useNavigation();
const handleRemoveIcon = async () => {
await AsyncStorage.removeItem(title, () => {
dispatch(receiveItemsAction());
navigation.goBack();
});
};
console.log(state);
return (
<View style={styles.deckPageContainer}>
<Deck
handleRemoveIcon={handleRemoveIcon}
title={title}
questions={questions}
date={date}
/>
</View>
);
};
-----------This is my ACTIONS file----------
import AsyncStorage from "#react-native-community/async-storage";
export const RECEIVE_ITEMS = "RECEIVE_ITEMS";
// export const REMOVE_ITEM = "REMOVE_ITEM";
export const receiveItemsAction = () => async (dispatch) => {
const objectValues = {};
try {
const keys = await AsyncStorage.getAllKeys();
if (keys.length !== 0) {
const jsonValue = await AsyncStorage.multiGet(keys);
if (jsonValue != null) {
for (let element of jsonValue) {
objectValues[element[0]] = JSON.parse(element[1]);
}
dispatch({
type: RECEIVE_ITEMS,
payload: objectValues,
});
} else {
return null;
}
}
} catch (e) {
console.log(e);
}
};
-----This is my REDUCERS file----
import { RECEIVE_ITEMS, REMOVE_ITEM } from "./actions";
const initialState = {
};
const items = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_ITEMS:
return {
...state,
...action.payload,
};
// case REMOVE_ITEM:
// return {
// ...state,
// ...action.payload,
// };
default:
return state;
}
}
export default items;
-----This is my UTILS file----
import AsyncStorage from "#react-native-community/async-storage";
export const removeDeckFromAsyncStorage = async (title)=>{
try{
await AsyncStorage.removeItem(title);
}
catch(e){
console.log(`Error trying to remove deck from AsyncStorage ${e}`);
}
}

Lodash debounce not working all of a sudden?

I'm using a component I wrote for one app, in a newer app. The code is like 99% identical between the first app, which is working, and the second app. Everything is fine except that debounce is not activating in the new app. What am I doing wrong?
// #flow
import type { Location } from "../redux/reducers/locationReducer";
import * as React from "react";
import { Text, TextInput, View, TouchableOpacity } from "react-native";
import { Input } from "react-native-elements";
import { GoogleMapsApiKey } from "../../.secrets";
import _, { debounce } from "lodash";
import { connect } from "react-redux";
import { setCurrentRegion } from "../redux/actions/locationActions";
export class AutoFillMapSearch extends React.Component<Props, State> {
textInput: ?TextInput;
state: State = {
address: "",
addressPredictions: [],
showPredictions: false
};
async handleAddressChange() {
console.log("handleAddressChange");
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleMapsApiKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
if (json.error_message) throw Error(json.error_message);
this.setState({
addressPredictions: json.predictions,
showPredictions: true
});
// debugger;
} catch (err) {
console.warn(err);
}
}
onChangeText = async (address: string) => {
await this.setState({ address });
console.log("onChangeText");
debounce(this.handleAddressChange.bind(this), 800); // console.log(debounce) confirms that the function is importing correctly.
};
render() {
const predictions = this.state.addressPredictions.map(prediction => (
<TouchableOpacity
style={styles.prediction}
key={prediction.id}
onPress={() => {
this.props.beforeOnPress();
this.onPredictionSelect(prediction);
}}
>
<Text style={text.prediction}>{prediction.description}</Text>
</TouchableOpacity>
));
return (
<View>
<TextInput
ref={ref => (this.textInput = ref)}
onChangeText={this.onChangeText}
value={this.state.address}
style={[styles.input, this.props.style]}
placeholder={"Search"}
autoCorrect={false}
clearButtonMode={"while-editing"}
onBlur={() => {
this.setState({ showPredictions: false });
}}
/>
{this.state.showPredictions && (
<View style={styles.predictionsContainer}>{predictions}</View>
)}
</View>
);
}
}
export default connect(
null,
{ setCurrentRegion }
)(AutoFillMapSearch);
I noticed that the difference in the code was that the older app called handleAddressChange as a second argument to setState. Flow was complaining about this in the new app so I thought async/awaiting setState would work the same way.
So changing it to this works fine (with no flow complaints for some reason. maybe because I've since installed flow-typed lodash. God I love flow-typed!):
onChangeText = async (address: string) => {
this.setState(
{ address },
_.debounce(this.handleAddressChange.bind(this), 800)
);
};