What is the best way to implement padding at the root of a react native app? - react-native

I would like to add margins/padding at the root of my app so that I do not have to include padding screen-by-screen.
My current entry point looks like this
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
};
//blah blah blah
render() {
return <AppNavigator />;
}
}
The entry point is currently handling the navigation to each screen throughout the app. Is there a way to drop styles into the root so that the parent view of every screen includes padding?
I'm currently styling screens individually, which I would rather simplify to avoid repetition, for example with a styles={styles.container} in the parent View on each screen.
My app is only about 6 screens, so the solution for global padding does not have to be the leanest solution possible, but something at the top level would be nice.
Thanks!

I assume that you would like to add padding to the content only. If you apply to navigator, your header and tabbar could be padded too.
You could define a Content component like this
function Content(props) {
return (
<View {...props} style={[{ padding: 10 }, props.style]}>
{props.children}
</View>
)
}
function YourScreen() {
return (
<Content>
<Text>This is content</Text>
</Content>
)
}

Related

How to reparent a component in ReactNative?

In the below code, I expected the webView content to not change when the clicks are increased, but every time it loads, a new timestamp is displayed.
const webView = (
<WebView
source={{
uri:
'data:text/html,<html><script>document.write("<h1 style=\\"font-size:64px\\">"+Date.now()+"<h1>");</script>',
}}
/>
);
export default class App extends React.Component {
state = {
clicks: 0,
};
onClick = () => {
this.setState({ clicks: this.state.clicks + 1 });
};
render() {
return (
<View>
<Text onPress={this.onClick}>
Click Me: {this.state.clicks}
</Text>
{this.state.clicks % 2 === 0 ? webView : null}
{this.state.clicks % 2 === 1 ? webView : null}
</View>
);
}
}
Link to expo snack to check it on a device.
So far, I've read about reparenting in React on issues here, implementing using Portals, and also saw an issue on supporting reparenting in react native with no resolution.
So, how to reuse a component instance in across multiple screens with out creating a new instance of it in every screen?
Was hoping reparenting would be the answer, but can't find any implementations, so if reparenting is the answer to this, how to implement it myself?
The problem here is that on every state change your component will re-render webView object and will show the current date. I suggest that you change webView to a component and add a static key when you call WebViewComp to prevent unmount/mount on every state change.
const WebViewComp = () => ( //Change declaration to function component instead of constant object
<WebView
source={{
uri:
'data:text/html,<html><script>document.write("<h1 style=\\"font-size:64px\\">"+Date.now()+"<h1>");</script>',
}}
/>
);
export default class App extends React.Component {
state = {
clicks: 0,
};
onClick = () => {
this.setState({ clicks: this.state.clicks + 1 });
};
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph} onPress={this.onClick}>
Click Me: {this.state.clicks}
</Text>
{this.state.clicks % 2 === 0 ? <WebViewComp key="child" /> : null}
{this.state.clicks % 2 === 1 ? <WebViewComp key="child" /> : null}
</View>
);
}
}
You definitely need to reparenting the view. I searched some libs that work as React Portals does.
We have two projects available:
https://github.com/zenyr/react-native-portal
https://github.com/mfrachet/rn-native-portals
I tested the second package (rn-native-portals) and this magically worked on Android:
How to install
npm install mfrachet/rn-native-portals
react-native link (unfortunately we can't auto-link this yet, but we can submit PR)
Implementation
Your target element needs to be inside <PortalOrigin>
import React from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { PortalOrigin } from 'rn-native-portals';
class Target extends React.Component {
state = {
moveView: false,
}
render() {
return (
<>
<TouchableOpacity
style={{ flex: 1 }}
onPress={() => this.setState({ moveView: !this.state.moveView })}
>
<Text>Press Here</Text>
</TouchableOpacity>
<PortalOrigin destination={this.state.moveView ? 'destinationPortal' : null}>
<View>
<Text>This text will appear on destination magically...</Text>
</View>
</PortalOrigin>
</>
);
}
}
export default Target;
On destination use this (don't forget set the same unique portal's name)
import React from "react";
import { PortalDestination } from "rn-native-portals";
class Destination extends React.Component {
render() {
return (
<PortalDestination name="destinationPortal" />
);
}
}
export default Destination;
This project is amazing, but definitely need our community help to create a better documentation.
I have one project that need to use this feature, reparenting a video to the outside of screen. I'm seriously considering PR auto-link support to avoid compiling warnings.
More useful info about:
The project concept:
https://github.com/mfrachet/rn-native-portals/blob/master/docs/CONCEPT.md
Why the project was created (long history):
https://tech.bedrockstreaming.com/6play/how-a-fullscreen-video-mode-ended-up-implementing-react-native-portals/
Haven't tried the accepted answer's projects but, for React Native, #gorhom/portal works like a charm retaining context like a champ!

React Native Web - Control the html tag generated by View?

I have a react native web project. At one point, I would like my View instead of generating a <div> to generate a <label> element.
Is there a way to control this? I am hoping for some sort of htmlTag attribute that gets ignored if this is not compiling for a browser environment.
There was a component prop but it's removed in favor of accessibilityRole (Remove component prop).
You can use accessibilityRole to specify the label tag and even create a custom element using it:
function Label({ text }) {
return <View accessibilityRole="label">{text}</View>
}
and then use it like this:
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View>
<Label text="Test input:"/>
<TextInput name="test-input"/>
</View>
</View>
);
}
}
Check this working Expo snack: https://snack.expo.io/#clytras/change-rn-web-div-tag
How about making your own wrapper of View? It could check what environment it is running in, and then if it's in a browser then you can return the label element instead.
You could also use the accessibilityLabel prop on the TextInput, which will add an aria-label prop to the React Native code when it compiles to html for web.
In terms of accessibility, this accomplishes the same effect as a label attribute. See the React Native web docs on accessibility: https://necolas.github.io/react-native-web/docs/accessibility/
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View>
<TextInput accessibilityLabel="test-input" name="test-input"/>
</View>
</View>
);
}
}

react native render another component in main

i want to render another component in the main component so user won't face a white screen for a second!
i'm using TabNavigator from react-navigation and when i want to switch between tabs, i face a white screen for a second (seems it need a second to render).
i was thinking of rendering the second tab in the first so i can have a better user experience!
P.s. : my components are in separate files!
Main:
export default class AdCalc extends React.Component {
render() {
return (
<View>
<Text>
TEST
</Text>
</View>
);
}
}
and my child:
export default class Child extends React.Component {
render() {
return (
<View>
<Text>
This is child!
</Text>
</View>
);
}
}
thanks in advance!
You can use react-navigation TabNavigatorConfig's lazy prop. Pass lazy={false} so that your views may load at initial start. Then you will not see such a screen.

React Native - how to scroll a ScrollView to a given location after navigation from another screen

Is it possible to tell a ScrollView to scroll to a specific position when we just navigated to the current screen via StackNavigator?
I have two screens; Menu and Items. The Menu is a list of Buttons, one for each item. The Items screen contain a Carousel built using ScrollView with the picture and detailed description of each Item.
When I click on a button in the Menu screen, I want to navigate to the Items screen, and automatically scroll to the Item that the button represent.
I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.
navigate('Items', { id: '1' })
So is this something that is possible in React Native and how do I do it? Or perhaps I'm using the wrong navigator?
Here's a dumbed down version of my two screens:
App.js:
const SimpleApp = StackNavigator({
Menu: { screen: MenuScreen},
Items: { screen: ItemScreen }
}
);
export default class App extends React.Component {
render() {
return <SimpleApp />;
}
}
Menu.js
export default class Menu extends React.Component {
constructor(props){
super(props)
this.seeDetail = this.seeDetail.bind(this)
}
seeDetail(){
const { navigate } = this.props.navigation;
navigate('Items')
}
render(){
<Button onPress={this.seeDetail} title='1'/>
<Button onPress={this.seeDetail} title='2'/>
}
}
Items.js
export default class Items extends React.Component {
render(){
let scrollItems = [] //Somecode that generates and array of items
return (
<View>
<View style={styles.scrollViewContainer}>
<ScrollView
horizontal
pagingEnabled
ref={(ref) => this.myScroll = ref}>
{scrollItems}
</ScrollView>
</View>
</View>
)
}
}
P.S I am specifically targeting Android at the moment, but ideally there could be a cross-platform solution.
I read that you can pass in parameters when using the StackNavigator like so: but I don't know how to read out that parameter in my Items screen.
That is achieved by accessing this.props.navigation.state.params inside your child component.
I think the best time to call scrollTo on your scrollview reference is when it first gets assigned. You're already giving it a reference and running a callback function - I would just tweak it so that it also calls scrollTo at the same time:
export default class Items extends React.Component {
render(){
let scrollItems = [] //Somecode that generates and array of items
const {id} = this.props.navigation.state.params;
return (
<View>
<View style={styles.scrollViewContainer}>
<ScrollView
horizontal
pagingEnabled
ref={(ref) => {
this.myScroll = ref
this.myScroll.scrollTo() // !!
}>
{scrollItems}
</ScrollView>
</View>
</View>
)
}
}
And this is why I use FlatLists or SectionLists (which inherit from VirtualizedList) instead of ScrollViews. VirtualizedList has a scrollToIndex function which is much more intuitive. ScrollView's scrollTo expects x and y parameters meaning that you would have to calculate the exact spot to scroll to - multiplying width of each scroll item by the index of the item you're scrolling to. And if there is padding involved for each item it becomes more of a pain.
Here is an example of scroll to the props with id.
import React, { Component } from 'react';
import { StyleSheet, Text, View, ScrollView, TouchableOpacity, Dimensions, Alert, findNodeHandle, Image } from 'react-native';
class MyCustomScrollToElement extends Component {
constructor(props) {
super(props)
this.state = {
}
this._nodes = new Map();
}
componentDidMount() {
const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
data.filter((el, idx) => {
if(el===this.props.id){
this.scrollToElement(idx);
}
})
}
scrollToElement =(indexOf)=>{
const node = this._nodes.get(indexOf);
const position = findNodeHandle(node);
this.myScroll.scrollTo({ x: 0, y: position, animated: true });
}
render(){
const data = ['First Element', 'Second Element', 'Third Element', 'Fourth Element', 'Fifth Element' ];
return (
<View style={styles.container}>
<ScrollView ref={(ref) => this.myScroll = ref} style={[styles.container, {flex:0.9}]} keyboardShouldPersistTaps={'handled'}>
<View style={styles.container}>
{data.map((elm, idx) => <View ref={ref => this._nodes.set(idx, ref)} style={{styles.element}}><Text>{elm}</Text></View>)}
</View>
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flexGrow: 1,
backgroundColor:"#d7eff9"
},
element:{
width: 200,
height: 200,
backgroundColor: 'red'
}
});
export default MyCustomScrollToElement;
Yes, this is possible by utilising the scrollTo method - see the docs. You can call this method in componentDidMount. You just need a ref to call it like: this.myScroll.scrollTo(...). Note that if you have an array of items which are all of the same type, you should use FlatList instead.
For iOS - the best way to use ScrollView's contentOffset property. In this way it will be initially rendered in a right position. Using scrollTo will add additional excess render after the first one.
For Android - there is no other option rather then scrollTo

Putting one Component in front of another (higher zIndex) dynamically

So there are two components sort of like this:
<View>
<Component1 />
<Component2 />
</View>
Both, Component1 & Component2 can be dragged and dropped within the View. By default, Component2 will render above the Component1 (since it is above in the stack). I want to make it so that whenever I press Component1 (for drag and drop) it dynamically comes infront of Component2 (Higher zIndex) and if I press Component1 and drag and drop, Component1 comes infront of Component2.
Anyone has any idea on how this can be done?
Edit 1:
I'm using zIndex, but for some reason it's working on iOS but not working on Android. Here's the basic code:
<View>
<View style={{position:'absolute',zIndex:2,backgroundColor:'red',width:500,height:500}}/>
<View style={{position:'absolute',zIndex:1,backgroundColor:'green',width:500,height:500,marginLeft:50}}/>
</View>
Setting dynamic zIndex for child components looks like the way to go. (zIndex on docs)
I would store the zIndexes of each child in the state. And I would wrap Component1 and Component2 with a touchable component if they are not already. When dragging & dropping starts, I'd update the zIndex stored in the state so that the required child would have higher zIndex.
Since I don't exactly know how you structured the components and their layouts, I am unable to provide a example code piece.
EDIT
Workaround for missing zIndex implementation on Android
I'd go something like this, if nothing else works:
import React from 'react';
import {
StyleSheet,
View,
} from 'react-native';
const style = StyleSheet.create({
wrapper: {
flex: 1,
position: 'relative',
},
child: {
position: 'absolute',
width: 500,
height: 500,
},
});
class MyComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
reverseOrderOfChildren: false,
};
}
render() {
const firstChild = <View style={[style.child, {backgroundColor: 'red'}]} />
const secondChild = <View style={[style.child, {backgroundColor: 'green'}]} />
if (this.state.reverseOrderOfChildren) {
return (
<View style={style.wrapper}>
{secondChild}
{firstChild}
</View>
);
}
return (
<View style={style.wrapper}>
{firstChild}
{secondChild}
</View>
);
}
}