Print react native text and QR generated - react-native

Hello I need to generate a PDF o HTML view to print a document from an Android device. So I started to use Print from 'expo-print'.
I can make this
Print.printAsync({
html: "<h3>Hello World</h3>"
});
And works just fine. But I want to generate and include a QR code inside that HTML, I wanted to use react-native-qrcode but I don't know how to include it inside that.
clarification: the QR code needs to be created without connection too
Thanks

I just came across the same problem and here's my solution.
I use react-native-qrcode-svg because it has a getRef props for you to further work with the QR data.
Below, you can find my rough implementation (My main code is on another computer). You can further customize it to hide QRCode component or using Redux to store QRData but it should work fine.
import QRCode from 'react-native-qrcode-svg';
...
constructor(props) {
super(props)
this.state = { qrData: "" }
}
componentDidMount () {
this.getDataURL(); // => Calling this in here to make sure the QRCode component did mount
}
... more code ...
print = () => {
Print.printAsync({
html: `
<h3>Hello World</h3>
<img src="data:image/jpeg;base64,${this.state.qrData}"/>
`
});
}
...
getDataURL() {
this.svg.toDataURL(this.callback);
}
callback(dataURL) {
this.setState({qrData: dataURL});
}
render() {
return (
<QRCode
value="Just some string value"
getRef={(c) => (this.svg = c)}
/>
<Button title="Print QR to HTML" onPress={this.print} />
);
}

You can use rn-qr-generator module (https://www.npmjs.com/package/rn-qr-generator). You need to pass value to the lib and it will return you uri and base64 data of the generated QRCode
import RNQRGenerator from 'rn-qr-generator';
componentDidMount() {
RNQRGenerator.generate({
value: 'otpauth://totp/Example:alice#google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example', // required
height: 300,
width: 300,
base64: false, // default 'false'
backgroundColor: 'black', // default 'white'
color: 'white', // default 'black'
})
.then(response => {
const { uri, width, height, base64 } = response;
this.setState({ imageUri: uri });
})
.catch(error => console.log('Cannot create QR code', error));
}

If the html code can be printed, it appears that the code used in html can be used. You will be able to use an img tag.
const htmlcode = '<html>'
+ '<head>'
+ '<title>Testing QR code</title>'
+ '</head>'
+ '<body>'
+ '<img id='barcode' src="https://api.qrserver.com/v1/create-qr-code/?data=HelloWorld&size=100x100" alt="" title="HELLO" width="50" height="50" />'
+ '</body>'
+ '</html>';
...
Print.printAsync({
html: htmlcode
});
Additional answers due to edited questions:
If you want to use QRCODE, you can't solve it with the module you want to use. Instead, you can solve this through 'React-native-web.' I will attach the link to the official document of Expo for the web setup. Once the setup is complete, use the existing app development method. However, the QRcode module does not currently work on the Web. The solution to this problem is to set QRCODE as an image file path, not as a web path in my first answer, and to show it when it's offline.
QRcode Example
import React, { Component } from 'react';
import QRCode from 'react-native-qrcode';
import { StyleSheet, View, TextInput } from 'react-native';
export default class HelloWorld extends Component {
state = {
text: 'testQRcode',
};
render() {
return (
<View style={styles.container}>
<TextInput
style={styles.input}
onChangeText={(text) => this.setState({text: text})}
value={this.state.text}
/>
<QRCode
value={this.state.text}
size={200}
bgColor='purple'
fgColor='black'/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center'
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
margin: 10,
borderRadius: 5,
padding: 5,
}
});

Related

How do I build a multi-card carousel?

I'm trying to build something like this in React Native. It will stretch across the whole page and will loop infinitely, there will be a 'next' and 'previous' button.
I'm new to React Native (coming from React), so am a little unsure about how to implement it.
I found this guide on YouTube helpful to get something very basic up and running.
Here is the code I have so far:
import React, {useCallback, useEffect, useRef, useState} from 'react';
import {withTheme} from 'react-native-paper';
import {
View,
StyleSheet,
Text,
Dimensions,
Image,
FlatList,
Pressable,
} from 'react-native';
import PrismicText from '../prismicText';
const {width: windowWidth, height: windowHeight} = Dimensions.get('window');
const Slide = ({data}) => {
return (
<View
style={{
height: 400,
width: 300,
justifyContent: 'center',
alignItems: 'center',
marginRight: 15,
}}>
<Image
source={{uri: data.image}}
style={{width: '100%', height: '100%', borderRadius: 16}}></Image>
</View>
);
};
const Carousel = ({slice, theme}) => {
const slideList = slice.items.map((item, index) => {
return {
id: index,
image: item.image.url,
};
});
const {colors, isTabletOrMobileDevice} = theme;
const styles = isTabletOrMobileDevice ? mobileStyles : desktopStyles;
const flatListRef = useRef(null);
const viewConfig = {viewAreaCoveragePercentThreshold: 50};
const [activeIndex, setActiveIndex] = useState(4);
const onViewRef = useRef(({changed}) => {
if (changed[0].isViewable) {
setActiveIndex(changed[0].index);
}
});
const handlePressLeft = () => {
if (activeIndex === 0)
return flatListRef.current?.scrollToIndex({
animated: true,
index: slideList.length - 1,
});
flatListRef.current?.scrollToIndex({
index: activeIndex - 1,
});
};
const handlePressRight = () => {
if (activeIndex === slideList.length - 1)
return flatListRef.current?.scrollToIndex({
animated: true,
index: 0,
});
flatListRef.current?.scrollToIndex({
index: activeIndex + 1,
});
};
return (
<>
<View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 8,
}}>
<Pressable style={[styles.chevron]} onPress={handlePressLeft}>
Left
</Pressable>
<Pressable style={[styles.chevron]} onPress={handlePressRight}>
Right
</Pressable>
</View>
<FlatList
ref={ref => (flatListRef.current = ref)}
data={slideList}
horizontal
showsHorizontalScrollIndicator={false}
snapToAlignment="center"
pagingEnabled
viewabilityConfig={viewConfig}
onViewableItemsChanged={onViewRef.current}
renderItem={({item}, i) => <Slide data={item} />}
keyExtractor={item => item}
/>
<View style={styles.index}>
<Text category={'c2'} style={styles.indexText}>
{activeIndex + 1} of {slideList.length} photos
</Text>
</View>
</>
);
};
const mobileStyles = StyleSheet.create({});
const desktopStyles = StyleSheet.create({});
export default withTheme(Carousel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The problems I'm experiencing with this code:
I am setting the initial state of the active index to 4, but active index always starts at 0
Clicking the 'right' button doesn't seem to change the active index
Clicking the 'right' button will move the carousel along by 1 increment, but it won't go any further (even on smaller viewports like mobile where you can only see 1.5 cards, so it should move along many times to be able to see all of the cards)
Clicking the 'left' button seems to have the same issues as above
There is no infinite loop of the slides
My feeling is that there are two issues to be addressed:
Active index is broken and needs to be fixed
Modifications required to make the number of cards on the viewport responsive
I've spent a lot of time looking at this and can't seem to figure it out. Any help would be much appreciated.
The first issue is easy to fix. You are expecting that the FlatList scrolls initially to the initial activeIndex, but you are not telling the FlatList to do so. There is a prop called initialScrollIndex that is designed for this purpose.
<FlatList
initialScrollIndex={4}
...
The second issue is caused by a faulty implementation of the functions handlePressLeft and handlePressRight as well as providing
const onViewRef = useRef(({changed}) => {
if (changed[0].isViewable) {
setActiveIndex(changed[0].index);
}
});
I have removed the above completely.
I have changed the activeIndex state to the following.
const [activeIndex, setActiveIndex] = useState({index: 4, direction: 'right'});
I have changed the handlePressLeft and handlePressRight functions to the following.
const handlePressLeft = () => {
setActiveIndex((prev) => ({index: prev.index - 1, direction: 'left'}));
};
const handlePressRight = () => {
setActiveIndex((prev) => ({index: prev.index + 1, direction: 'right'}));
};
I have created an effect as follows.
React.useEffect(() => {
if (activeIndex.index === slideList.length - 1 && activeIndex.direction === 'right') {
setActiveIndex({index: 0, direction: 'right'});
} else if (activeIndex.index < 0 && activeIndex.direction === 'left') {
setActiveIndex({index: slideList.length - 2, direction: 'left'})
} else {
flatListRef.current?.scrollToIndex({
animated: true,
index: activeIndex.index,
});
}
}, [activeIndex, slideList.length]);
I have implemented an adapted snack without images and using a dummy array.
You can use the below third-party library to achieve the above one quickly.
react-native-snap-carousel
You can check all the examples and use them according to your requirement.
Hope it will help you!
You should try react-native-reanimated-carousel.
Why?
highly customizable + easy and fast to implement any carousel
It's new and it uses react-native-reanimated for better performance (by running animations on the UI thread, rather than on JS thread)
solves all the issues that react-native-snap-carousel has (which is deprecated and has lots of bugs)
solves all the issues that you have and handles many edge cases that you may have forgotten about (in case you want to implement it by yourself)

swiping on react-native-snap-carousel is not working as expected

I am trying to use react-native-snap-carousel but however, the swiping effect is not working as expected - it is often difficult to swipe left and right, it requires user to swipe harder to move to another picture (as illustrated in the link below).
Swiping issue with React Native Snap Carousel
I am not able to find any documented soluton but I found one possible prop - swipeThreshold. I try various value, but still the issue persist.
Does anyone know the solution to this?
I suggest you to use react-native-image-slider.
it's flexible and easy to use.
https://www.npmjs.com/package/react-native-image-slider
I made a component named slider.js:
import React, { Component } from 'react';
import {
View,
StyleSheet,
Image,
} from 'react-native';
import ImageSlider from 'react-native-image-slider';
export default class Slider extends Component {
render() {
return (
<ImageSlider
loop
autoPlayWithInterval={3000}
images={this.props.dataSource}
customSlide={({ index, item, style, width }) => (
<View key={index} style={[style, styles.customSlide]}>
<Image source={{ uri: item }} style={styles.customImage} />
</View>
)}
/>
);
}
}
const styles = StyleSheet.create({
customImage: {
height: 180,
marginRight: 20,
marginLeft: 20,
borderWidth: 1,
borderRadius: 10,
marginTop: 8,
},
customSlide: {
backgroundColor: '#eee',
},
});
you can add this to your project and use it wherever you need it like this:
import Slider from '../component/slider';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
images: [
'https://placeimg.com/640/480/nature',
'https://placeimg.com/640/480/tech',
'https://placeimg.com/640/480/animals',
'https://placeimg.com/640/480/tech',
],
}
render() {
return (
<View style={{flex: 1, backgroundColor: '#eee'}}>
<Slider dataSource={this.state.images} />
</View>
);
}
}

How to share an image to Instagram Stories from Expo / React Native

How do I share an image directly to an Instagram story using Expo / React Native?
This issue has already been solved for iOS for normal Instagram posts, but not stories.
Facebook's docs explain how to do this for iOS and Android, but not React Native.
Expected behavior:
Open Instagram story with the selected image.
Current behavior:
Opens Instagram story with blank screen.
The problem I'm facing:
I'm not quite sure how to generate a URI that fits Instagram's schema, as referenced in their docs.
Reproducible Snack
https://snack.expo.io/#nandorojo/share-to-instagram-story
Thank you so much!
Here is my code from the snack above:
import * as React from 'react';
import { Text, View, StyleSheet, Linking, Image } from 'react-native';
import * as FileSystem from 'expo-file-system';
const url = 'https://source.unsplash.com/daily';
export default class App extends React.Component {
_shareToStory = async () => {
// download to device
const { uri } = await FileSystem.downloadAsync(
url,
`${FileSystem.documentDirectory}meme.jpg`
).catch(e => console.log('instagram share failed', JSON.stringify(e), url));
try {
const encodedUrl = encodeURIComponent(uri);
const instagramUrl = `instagram-stories://share?backgroundImage=${encodedUrl}`;
Linking.openURL(instagramUrl);
} catch (error) {
console.log(error);
}
};
render() {
return (
<View style={styles.container}>
<Image source={{ uri: url }} style={{ height: 100, width: 100 }} />
<Text style={styles.paragraph} onPress={this._shareToStory}>
Share to Instagram Story
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
If you're using Expo Managed Workflow, your options are very limited. As of SDK v38, afaik it's only possible to accomplish this on Android, and only if you're sharing a background image (due to missing Expo APIs for content permissions and pasteboard), then something like this might work:
import * as FileSystem from 'expo-file-system'
import * as IntentLaucher from 'expo-intent-launcher'
// url of the file to share
const url = '....'
// some random temporary filename
const localFile = `${FileSystem.cacheDirectory}${uuid()}.jpg`
try {
FileSystem.downloadAsync(url, localFile)
.then(({ uri }) => {
FileSystem.getContentUriAsync(uri).then(contentUri => {
IntentLauncher.startActivityAsync(
'com.instagram.share.ADD_TO_STORY',
{
data: contentUri,
flags: 1, // FLAG_GRANT_READ_URI_PERMISSION
type: 'image/jpeg', // or other based on your file type
}
).catch(console.warn)
})
})
.catch(console.warn)
} finally {
FileSystem.deleteAsync(localFile).catch(console.warn)
}
However, if you're using React Native (or have ejected to Expo Bare Workflow), then you have more options. I'd recommend using the RN community supported react-native-share library that works on both Android and iOS and supports all of the sharing options (plus many other services):
import Share from 'react-native-share'
Share.shareSingle({
method: Share.InstagramStories.SHARE_BACKGROUND_IMAGE,
backgroundImage: '....' // url of the file to share
social: Share.Social.INSTAGRAM_STORIES,
})

React Native: I am getting error while trying to get image from https://cataas.com api

I am getting SyntaxError: Json Parse error: JSON Parse error: Unrecognized token '<'
I'm using https://cataas.com api for a react native app, my task is to generate a list of random kitten images. I tried using fetch method, but also i get error sorce.uri should not be an empty string. How can i solve this problem?
Here is my code:
import React, { Component } from 'react';
import {
Image,
StyleSheet,
Text,
View,
FlatList
} from 'react-native';
class App extends Component {
state = {
photos: '',
}
componentDidMount() {
fetch('https://cataas.com/cat?width=100')
.then(res => res.json())
.then(data => {
this.setState({
photos: data
})
.catch(err => {
console.log('error', err);
alert(err)
})
})
}
render() {
console.log(this.state.photos)
return (
<View style={styles.container}>
<Image
source={{url: this.state.photos}}
style={{height: 100, width: 100}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ecf0f1',
}
});
export default App;
There is a typo in your code
Replace url with uri as in the docs
<Image
source={{uri: this.state.photos}}
style={{height: 100, width: 100}}
/>
You don't have to call this api manually, you could directly use the link in the Image component :
<Image
source={{uri: "https://picsum.photos/100/100"}}
style={{height: 100, width: 100}}
/>
EDIT:
Ok it's not as easy as I thought !
I created a first basic version : https://snack.expo.io/#sanjar/so-53434400
And contrary to what I thought it's always the same picture that is displayed.
It's because of react-native cache system that see the same url and decide to not execute the http request again.
then I checked the doc and founda way to fix this issue, but for ios only
I just had to change :
source={{uri: "https://source.unsplash.com/random"}}
by :
source={{uri: "https://source.unsplash.com/random", cache: 'reload'}}
It should work on ios (I don't have a mac with me now), for android I don't know yet, I'll probably investigate later.

Bug of React Native FlatList

I have a problem with React Native FlatList,
export default class PoolList extends Component {
constructor(props) {
super(props)
this.state = {
data: [
{key: 1, img: './resources/image1.png', txt: 'Text 1'},
{key: 2, img: './resources/image2.png', txt: 'Text 2'},
{key: 3, img: './resources/image3.png', txt: 'Text 3'}
]
}
}
render() {
return (
<View style={styles.view}>
<FlatList
data={this.state.data}
renderItem={({item}) =>
<View style={styles.flatListItem}>
<Image source={require(item.img)} />
<Text>{item.txt}</Text>
</View>
}
/>
</View>
);
}
}
I got a bug when run it
require() must have a single string literal argument
But when I change <Image source={require(item.img)} /> to <Image source={require('./resources/image1.png')} />, It works. Can someone explain to me why. I need to make a FlatList with Image dynamic, Thanks
It took me quite a while to figure out a workaround to this problem. Don't worry, require() must have a single string literal argument is a really common problem in the JavaScript community because require has a lot of issues with variables being passed in as the string argument.
This is the best workaround I could come up with.
So instead of using a flatlist, I decided to use a map. First off, you need to create constants for each of the images you want to dynamically import from a local directory. Then you need to create an array of objects so each object will be one of your image constants. Now, iterate through each entry using the map and generate your <Image> components. Finally, render the variable with all the components you just created.
To view the full code for the solution I wrote, visit the ExpoSnack I created.
https://snack.expo.io/HyNO-pLQG
SnackExpo also has an in-browser device simulator included.
To run this app on your physical device, you can download the Expo app and then scan the QR code provided by the ExpoSnack.
import React, {Component} from 'react';
import {View, Image, StyleSheet} from 'react-native';
const image1 = require('./resources/image1.png');
const image2 = require('./resources/image2.png');
const image3 = require('./resources/image3.png');
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
image: {
width: 200,
height: 200,
},
});
var dataArray = [
{data: image1},
{data: image2},
{data: image3},
];
var theReturnData = dataArray.map((item) => (
<View key={item}>
<Image
style={styles.image}
source={item.data}
/>
</View>
));
export default class App extends Component {
render() {
return (
<View style={styles.container}>
{theReturnData}
</View>
);
}
}