React-PDF stripping style from nested components output by render callback - html

I am attempting to pass a shared component to a <View fixed> element, however it appears that the output has missing style properties...
It appears that the child component's styling is preserved, but any grandchildren do not...
Further investigation shows that even hardcoding all of the <View>, <Text> and style also results in nested elements being stripped...
React-PDF Repl
Is there any reason why this is happening? Is there a way in which I can get the styles included?
const TableHeaders = () => (
<TableRow>
{['name', 'age', 'location'].map(label =>
<TableCell>{label}</TableCell>)}
</TableRow>
);
const TableRow = ({children}) => <View style={styles.row}>{children}</View>;
const TableCell = ({children}) => (
<View style={styles.column}>
<Text>{children}</Text>
</View>
);
const FixedHeader = () => <View fixed render={() => <TableHeaders />} />;
const HardcodedHeader = () => (
<View render={() => (
<View style={styles.row}>
{['name', 'age', 'location'].map(label => (
<View style={styles.column}>
<Text>{label}</Text>
</View>
))}
</View>
)} />
)
const HardcodedHeaderWithHardcodedStyles = () => (
<View render={() => (
<View style={{
flexDirection: 'row',
backgroundColor: 'lightblue',
borderRadius: 4,
margin: '2px 0',
padding: '10px 7px'
}}>
{['name', 'age', 'location'].map(label => (
<View style={{
margin: '0 5px',
fontSize: 10,
color: 'red'
}}>
<Text>{label}</Text>
</View>
))}
</View>
)} />
)
const Quixote = () => (
<Document>
<Page style={styles.body}>
<TableHeaders />
<FixedHeader />
<HardcodedHeader />
<HardcodedHeaderWithHardcodedStyles />
</Page>
</Document>
);
const styles = StyleSheet.create({
column: {
margin: '0 5px',
fontSize: 10,
color: 'red'
},
row: {
flexDirection: 'row',
backgroundColor: 'lightblue',
borderRadius: 4,
margin: '2px 0',
padding: '10px 7px'
},
body: {
paddingTop: 35,
paddingBottom: 65,
paddingHorizontal: 35,
}
});
ReactPDF.render(<Quixote />);

SOLVED!
It turns out that yes, using the render={() => {}} prop to render elements with children does cause some headaches if you're not careful.
Margin and Padding
For Margin and Padding I was originally defining the values using a string format mirroring how we would do it within a proper css file:
{
[margin|padding]: '[vertical]px [horizontal]px'
}
This must somehow get garbled by the render process and is misunderstood. After a lot of experimenting, it turns out that if we split out the definitions into their compoent parts and pass number values rather than strings, it's actually carried through properly (note: Numbers are interpreted as 'px' unless otherwise configged):
{
marginTop: 2,
marginBottom: 2,
paddingTop: 10,
paddingBottom: 10,
paddingLeft: 7,
paddingRight: 7
}
Fonts
Fonts are a little more strange. It seems that the render callback method somehow strips the association of fontFamily, fontSize, fontWeight etc from any child element from its parent. This means that we have to either redefine these parts in the <Text> element we want to style, or we can move the original definitions down to those <Text> elements from their <View> containers.
Persisting issues
The only thing that I couldn't get to work was the borderRadius.
See the new, updated Repl

Related

Is this inline block+text layout possible at all in React Native?

I'm trying to achieve the following layout in React Native (layout created successfully on web in HTML+CSS):
The tricky part is the gray breadcrumb element that should inline with the description. My code for the breadcrumb+description part is as follows:
<Text style={styles.description}>
<Text style={styles.quantity}>{quantity}</Text>
{description}
</Text>
styles.ts:
description: {
fontSize: 14,
fontFamily: 'light',
flexDirection: 'row',
flexWrap: 'wrap',
},
quantity: {
fontSize: 13,
color: Color.white,
backgroundColor: Color.placeholder,
borderRadius: 16,
paddingHorizontal: 400,
},
It results in this:
As you can see, the borderRadius and padding properties get ignored on the quantity element.
I have also tried doing it with View elements instead of Text ones but the description simply goes fully to the next line when it overflows (classic block behaviour).
So, is there any trick/workaround to achieving this layout in RN? Thanks.
Wrap the nested text in a view with a flex direction of row, and then go about wrapping the text component in a view (demo):
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { Text } from 'react-native-paper';
const Color = {
white: 'white',
placeholder: 'blue',
};
const quantity = 50;
const description =
'Once upon a time there was a story told that took a very long time to tell.';
const InlineText = ({ style, children, ...props }) => {
style = StyleSheet.flatten(style)
const textStyle = {
...style,
// // remove styles that dont work on android/ios
paddingHorizontal:null,
marginHorizontal:null,
borderRadius:null
}
return (
<View style={[{alignItems:'center',justifyContent:'center'},style]}>
<Text {...props} style={textStyle}>{children}</Text>
</View>
);
};
export default function App() {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<Text style={styles.description}>
{/*InlineText no longer inherits the parent text styles*/}
<InlineText style={[styles.description,styles.quantity]}>{quantity}</InlineText>
{description}
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
description: {
fontSize: 14,
fontFamily: 'light',
flexDirection: 'row',
flexWrap: 'wrap',
},
quantity: {
fontSize: 13,
color: Color.white,
backgroundColor: Color.placeholder,
borderRadius: 16,
paddingHorizontal: 5,
marginHorizontal: 3,
},
});

React Native. Show component next to the marker

Is there a way to create a popup similar to the one shown below .
Im using react-native-maps, google provider and markers which are working well, it is only the popup next to the marker that i am having issues with
We can use the Callout component to do this.
The Callout component accepts a custom view and is flexible in what content it accepts:
Callouts to markers can be completely arbitrary react views, similar to markers. As a result, they can be interacted with like any other view.
Source: https://github.com/react-native-community/react-native-maps#custom-callouts
So an example fitted to your use case looks something like this:
const CustomCalloutView = ({ marker }) => (
<View>
<View style={{ flex: 1, flexDirection: "row" }}>
<Text>Kyiv Ukraine</Text>
<Badge value="2" status="warning" />
</View>
<Text>+1°</Text>
</View>
);
// ...
<Marker coordinate={marker.latlng}>
<Callout>
<CustomCalloutView marker={{...marker}} />
</Callout>
</Marker>
For the badge I've used the badge the react-native-elements library provides (https://react-native-elements.github.io/react-native-elements/docs/badge), but you can change this to whatever you want.
To make the CustomCalloutView content dynamic based on the marker coordinates passed as props you could use the function reverseGeocodeAsync from expo-location to get info about the location.
Source: https://docs.expo.io/versions/latest/sdk/location/#locationreversegeocodeasynclocation.
Example using dynamic marker coordinates and expo-location:
import * as Location from "expo-location";
// ...
const CustomCalloutView = ({ marker }) => {
const [location, setLocation] = useState(null);
useEffect(() => {
Location.reverseGeocodeAsync(marker).then(res => {
setLocation(res[0]);
});
}, []);
return (
<View>
{location && (
<View>
<View style={{ flex: 1, flexDirection: "row" }}>
<Text>{`${location.city}, ${location.country}`}</Text>
<Badge value="2" status="warning" />
</View>
<Text>+1°</Text>
</View>
)}
</View>
);
};

Use a function sent from Flatlist to a sub component

I'm trying to use a function from a class into another, when dealing with Flatlist.
Here is my class where my flatlist is :
_displayDetailsForMyItem = (idItem, type) => {
this.props.navigation.navigate('ItemDetails', { idMyItem: idItem, type: type })
}
_displayDataList(){
const type = this.props.route.params?.type ?? 'defaultValue'
if (type === 'Money') {
return this.state.moneyList
} else if (type === 'Stuff') {
return this.state.stuffList
} else {
return this.state.peopleList
}
}
_deleteItem = (idItem, type) => {
alert('Delete button pressed')
}
render(){
const type = this.props.route.params?.type ?? 'defaultValue'
if(this.state.isLoading){
return(
<View style={styles.activity}>
<ActivityIndicator size="large" color="#ED6D6D"/>
</View>
)
}
return(
<FlatList
style={styles.list}
data={this._displayDataList()}
keyExtractor={(item) => item.key.toString()}
renderItem={({item}) => <MyItem
myItem={item}
itemType={type}
displayDetailsForMyItem={this._displayDetailsForMyItem}
deleteItem={(item) => this._deleteItem(item.id, type)}/>}
onEndReachedThreshold={0.5}
onEndReached={() => {
}}
/>
)
}
I don't put all the code since it's not relevant.
In another class I have the item part of my code.
render(){
const { myItem, displayDetailsForMyItem } = this.props
let ptfPrefix
(Platform.OS === 'android') ? ptfPrefix = 'md-' : ptfPrefix = 'ios-'
const calIconName = ptfPrefix + 'calendar'
const titleIconName = ptfPrefix + 'create'
const pplIconName = ptfPrefix + 'contact'
const RightActions = (progress, dragX) => {
const scale = dragX.interpolate({
inputRange: [-100, 0],
outputRange: [0.7, 0]
})
return (
<>
<TouchableOpacity onPress={() => deleteItem(myItem.key, 'toto')}>
<View
style={{ flex: 1, backgroundColor: 'red', justifyContent: 'center' }}>
<Animated.Text
style={{
color: 'white',
paddingHorizontal: 20,
fontWeight: '600',
transform: [{ scale }]
}}>
<Ionicons name='ios-trash' size={70} color='#FFFFFF' />
</Animated.Text>
</View>
</TouchableOpacity>
</>
)
}
return(
<Swipeable renderRightActions={RightActions}>
<TouchableOpacity
style={styles.main_container}
onPress={() => displayDetailsForMyItem(myItem.key)}>
<View style={styles.first_line}>
<View style={styles.left_part_container}>
<View style={styles.date_container}>
<Image style={styles.date_bg} source={require('../assets/icons/list_bg.png')} />
<Ionicons name={calIconName} style={styles.top_left_elmnts} />
<Moment style={styles.top_left_elmnts} element={Text} format="DD/MM/YYYY" date={myItem.date} />
</View>
</View>
<View style={styles.right_part_container}>
<Text style={styles.top_right_elmnts}>{myItem.title}</Text>
<Ionicons name={titleIconName} style={styles.top_right_elmnts} />
</View>
</View>
<View style={styles.main_data}>
<Text style={styles.main_text}>
{(this.props.itemType === 'Money') ? myItem.amount + " €" : myItem.quantity + " Objets"}
</Text>
</View>
<View style={styles.last_row}>
<View style={styles.left_part_container}>
<Ionicons name={pplIconName} style={styles.btm_left_elmnts} />
<Text style={styles.btm_left_elmnts}>{myItem.people}</Text>
</View>
</View>
</TouchableOpacity>
</Swipeable>
)
}
So here, I pass 2 functions from the Flatlist class to the Item Class (two distinct .js files for me).
The displayDetailsForMyItem(myItem.key) function is correctly working (seems that it's because I'm calling it from the HTML part).
The fact is I would like to be able to call the second function deleteItem from outside the render() part (within the onPress() of the TouchableOpacity which is in the const RightActions), but I have an error telling :
can't find variable: deleteItem
I'm stuck
Ok I just didn't test all the possibilities.
Just have to call this.props.myFunction() instead of just myFunction (which is working inside the HTML part)

How to set search image icon in react-native-google-places-autocomplete?

How to set search image icon in react-native-google-places-autocomplete?
I want to know how i can set search image icon on left side in react-native-google-places-autocomplete.
I want output like this in below screen.So please help me.
But my design is like this.
Here is code of DiscoveryLocation.js file.
import React, { Component } from 'react'
import { Text, View, TouchableOpacity, TextInput, StyleSheet, Image } from 'react-native'
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { RFPercentage, RFValue } from "react-native-responsive-fontsize";
import MapView, { PROVIDER_GOOGLE, Marker } from 'react-native-maps';
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
console.disableYellowBox = true;
export default class DiscoveryLocation extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.vwheader} >
<TouchableOpacity
onPress={() => { this.props.navigation.goBack() }}
>
<Image source={require('../../Images/left-arrow-red.png')} style={{ height: 25, width: 30, marginTop: 22, marginLeft: 15, }}
/>
</TouchableOpacity>
<Text style={styles.txtdisloc}>Discovery Location </Text>
</View>
<View style={styles.mapcontainer}>
<MapView
provider={PROVIDER_GOOGLE}
style={styles.map}
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
}}
>
</MapView>
<View style={{ marginTop: hp('12%'), }}>
<GooglePlacesAutocomplete
placeholder='Enter City, State, Country'
minLength={2} // minimum length of text to search
autoFocus={false}
fetchDetails={true}
onPress={(data, details = null) => { // 'details' is provided when fetchDetails = true
console.log(data);
console.log(details);
}}
getDefaultValue={() => {
return 'Mataram';
}}
query={{
// available options: https://developers.google.com/places/web-service/autocomplete
key: 'MY_API_KEY',
language: 'en', // language of the results
types: '(cities)' // default: 'geocode'
}}
styles={{
textInputContainer: {
width: wp('90%%'), height: hp('7%'), borderRadius: 11, borderTopWidth: 0,
borderBottomWidth: 0
},
textInput: {
marginLeft: 0,
marginRight: 0,
backgroundColor: 'D3D3D3'
},
description: {
fontWeight: 'bold',
},
predefinedPlacesDescription: {
color: '#1faadb'
},
powered: {
},
}}
filterReverseGeocodingByTypes={['locality', 'administrative_area_level_3']} // filter the reverse geocoding results by types - ['locality', 'administrative_area_level_3'] if you want to display only cities
predefinedPlacesAlwaysVisible={true}
/>
</View>
</View>
<View style={{ marginTop: 10, marginBottom: 10 }}>
<TouchableOpacity style={styles.btn}>
<Text style={styles.txtbtn}>Confirm Location</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
I know this is late, but for the future ref There are props to add icons in GooglePlacesAutocomplete
renderLeftButton={() => <Image source={require('path/custom/left-icon')} />}
renderRightButton={() => <Text>Custom text after the input</Text>}
Here is the link for tutorial

Need to dynamically add items into a drawer menu in React Native

I need to have some items dynamically in my app's drawer after some categories get fetched from a json file (https://www.rallyssimo.it/wp-json/wp/v2/categories)
json example (I need that information)
[
{
"id": 44,
.
.
"name": "ALTRI RALLY",
.
.
},
Tis is the drawer:
const CustomDrawerComponent = (props) => (
<SafeAreaView style={{flex:1}}>
<View style={{height:80, backgroundColor: 'white', alignItems: 'center', justifyContent: 'center'}}>
<Image
source={{uri: 'https://www.rallyssimo.it/wp-content/uploads/2016/08/rallyssimo-logo.png'}}
style={{ height: 60, width: 180}}
/>
</View>
<ScrollView>
<DrawerItems {...props} />
</ScrollView>
</SafeAreaView>
)
const AppNavigator = createDrawerNavigator(
{
Home: DashboardStackNavigator,
},
{
contentComponent: CustomDrawerComponent
}
);
const AppContainer = createAppContainer(AppNavigator);
//Main class
export default class App extends React.Component {
render() {
return <AppContainer />;
}
}
How can I put the items (I'm going to get from the JSON) in the drawer?
As you have noticed, you need to create your own custom drawer to achieve this, which is done with contentComponent: CustomDrawerComponent.
Now you cannot use DrawerItems within CustomDrawerComponent since you want full control on the items listed. But you can recreate the items yourself using basic and elements.
Finally you need to fetch the API and store the data in your state in order to render the result as a list in the drawer.
Here is a basic example for :
import React, { Component } from 'react';
import { ScrollView, Text, View, Image } from 'react-native';
import { NavigationActions } from 'react-navigation';
class CustomDrawerComponent extends Component {
constructor(props) {
super(props);
this.state = { data: null };
}
async componentDidMount() {
fetch('https://www.rallyssimo.it/wp-json/wp/v2/categories')
.then(res => res.json())
.then(data => this.setState({ data }))
}
navigateToScreen(routeName, params) {
return () => { this.props.navigation.dispatch(NavigationActions.navigate({ routeName, params })) };
}
render() {
if (this.state.data === null) {
return <Text>...</Text>;
}
return (
<View style={{ flex: 1, paddingTop: 30 }}>
<View style={{height:80, backgroundColor: 'white', alignItems: 'center', justifyContent: 'center'}}>
<Image
source={{uri: 'https://www.rallyssimo.it/wp-content/uploads/2016/08/rallyssimo-logo.png'}}
style={{ height: 60, width: 180}}
/>
</View>
<ScrollView>
<View>
{this.state.data.map(x => (
<Text
key={x.id}
style={{ fontSize: 16, lineHeight: 30, textAlign: 'center' }}
onPress={this.navigateToScreen('page2')}
>
{x.name}
</Text>
))}
</View>
</ScrollView>
</View>
);
}
}
export default CustomDrawerComponent;
And here is a working snack.