What is the point of StyleSheet.create - react-native

I'm reading the React Native docs / tutorial, and I'm wondering what the point of the StyleSheet.create function is.
For example, the tutorial has the following code:
const styles = StyleSheet.create({
bigblue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
});
But I don't understand the difference between that and:
const styles = {
bigblue: {
color: 'blue',
fontWeight: 'bold',
fontSize: 30,
},
red: {
color: 'red',
},
};

TL;DR Always use StyleSheet.create() when you can.
The answer by Nico is correct, but there is more to it.
To summarize:
It validates the styles as mentioned by Nico
As mentioned in the documentation:
Making a stylesheet from a style object makes it possible to refer to it by ID instead of creating a new style object every time.
Also mentioned in the documentation:
It also allows to send the style only once through the bridge. All subsequent uses are going to refer an id (not implemented yet).
As you might know, sending the data across the bridge is a very costly operation that has significant impact on the performance of the application. So, using StyleSheet.create() you reduce the strain on the bridge.

StyleSheet.create does not add performance gains anymore.
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/29265#issuecomment-430783289
quoting the github comment:
#alloy I understand what docs says, but can prove that code:
const myStyle: ViewStyle = { flex: 1 } export const FlexView:
React.SFC = (props) => <View style={myStyle}>{props.children}</View>
has almost same performance (even slightly faster) compared to
const s = StyleSheet.create({ flex: { flex: 1 } })
export const FlexView: React.SFC = (props) => <View style={s.flex}>{props.children}</View>
because if you look at sources, you discover that latest chunk effectively extracted to this (see:
https://github.com/facebook/react-native/blob/0.57-stable/Libraries/StyleSheet/StyleSheet.js#L373):
const s = { flex: { flex: 1 } }
export const FlexView = (props) => <View style={s.flex}>{props.children}</View>
And yes, in previous
versions of RN it was global registry of styles, but it was even more
slow, because it never crossed bridge border actually (proof from 0.55
branch) 😀

Here is there source code of create.
create<T: Object, U>(obj: T): {[key:$Keys<T>]: number} {
var result: T = (({}: any): T);
for (var key in obj) {
StyleSheetValidation.validateStyle(key, obj);
result[key] = ReactNativePropRegistry.register(obj[key]);
}
return result;
}
I am not an expert of React in any. I actually never used it but here are my insights. It seems that create does some kind of validation over your keys and register them to React.
I think you could skip the validation by simply not calling create but I'm not sure what ReactNativePropRegistry.register does exactly.
Reference to the source

As #Mentor pointed out in the comments:
.create still only validates in development and does nothing else. In production it just returns the object. See source code in repository.
source code
create<+S: ____Styles_Internal>(obj: S): $ObjMap<S, (Object) => any> {
if (__DEV__) {
for (const key in obj) {
StyleSheetValidation.validateStyle(key, obj);
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
}
I think this comment deserves to be more noticeable. So I post it as an answer.
Additionally, I'd like to point out that validation - is a good thing, but there is another, better way to validate - Typescript:
const styles = StyleSheet.create({
someViewStyle: { ... },
someTextStyle: { ... },
})
can be replaced with
import { ..., ViewStyle, TextStyle } from 'react-native';
interface Styles {
someViewStyle: ViewStyle,
someTextStyle: TextStyle,
}
const styles = {
someViewStyle: { ... },
someTextStyle: { ... },
}
And it's not just static-time check, it also allows to discriminate between ViewStyle and TextStyle.
But there are more lines of code. So, personally, I prefer to go without styles object, if possible:
const someViewStyle: ViewStyle = { ... },
const someTextStyle: TextStyle = { ... },

Related

How to use props in React Native?

I am new to React Native. I am wondering how to use props to work my code? Here is my code.
const weatherConditions = {
Rain: {
color: '#005BEA',
},
Clear: {
color: '#f7b733',
}
};
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.weather.color }
]}/>
);
};
But it does not work. Only the code below works. How to fix this? Help me.
const Weather = ({ weather }) => {
return (
<View
style={[
styles.weatherContainer,
{ backgroundColor: weatherConditions.Rain.color } // or { backgroundColor: weatherConditions.Clear.color }
]}
>
);
};
Your problem is that you are telling your component to look for weatherConditions.weather.color but the key weather is being interpreted literally. The component is looking for the weather key inside weatherConditions and it won't find it.
What you need to do is to do is:
backgroundColor: weatherConditions[weather].color
The brackets ensure weather is interpreted as a variable and not the word weather.
You pass the weather conditions via the prop weather and not weatherConditions. Can you try it with weather.Rain.color? It should work with that.

React: how to initialise redux state before rendering component?

I'm trying to create a basic app with a user login feature using Redux to manage the user details. I've linked a GIF of my screen below and, as you can see, there is a delay between loading the component and the user details rendering. My code for the component profile is also noted.
Name of user delay when loading
import React, {Component} from 'react';
import {View, StyleSheet, Text, TouchableOpacity} from 'react-native';
import {connect} from 'react-redux';
import {fetchProfile} from '../../actions/ProfileActions';
import {logoutUser} from '../../actions/AuthActions';
class Profile extends Component {
state = {
firstName: '',
lastName: '',
email: '',
goals: '',
};
componentDidMount() {
this.props.fetchProfile();
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.setState({
firstName: this.props.profile.firstName,
lastName: this.props.profile.lastName,
email: this.props.profile.email,
goals: this.props.profile.goals,
});
}
}
onPressLogout = () => {
this.props.logoutUser();
};
render() {
return (
<View style={styles.container}>
<View style={styles.headerContainer}>
<Text style={styles.header}>
Profile of {this.state.firstName} {this.state.lastName}
</Text>
</View>
<View style={styles.textContainer}>
<TouchableOpacity
style={styles.buttonContainer}
onPress={this.onPressLogout.bind(this)}>
<Text style={styles.buttonText}>Logout</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const mapStateToProps = (state) => ({
profile: state.profile.profile,
});
export default connect(mapStateToProps, {fetchProfile, logoutUser})(Profile);
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F9F9F9',
},
headerContainer: {
marginTop: 75,
marginLeft: 20,
},
header: {
fontSize: 34,
fontWeight: '700',
color: '#000000',
},
textContainer: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 30,
},
buttonContainer: {
backgroundColor: '#34495E',
alignItems: 'center',
padding: 12,
width: 350,
borderRadius: 15,
},
buttonText: {
color: 'white',
fontWeight: 'bold',
fontSize: 19,
},
});
EDIT: I forgot to explain what fetchProfile() does. It connects to the firebase database to retrieve the user's details. Code below:
import {PROFILE_FETCH} from './types';
import firebase from 'firebase';
export const fetchProfile = () => {
const {currentUser} = firebase.auth();
return (dispatch) => {
firebase
.database()
.ref(`/users/${currentUser.uid}/profile`)
.on('value', (snapshot) => {
dispatch({
type: PROFILE_FETCH,
payload: snapshot.val(),
});
});
};
};
Furthermore, I have 3 different screens in the app, all of which will probably make use of the user's details. I'm sure there must be a more efficient way of only having to fetchProfile() once and then passing the details to each component, somehow...
How can I have it so when the user logs in, their details are already loaded in the component, so there is no delay? Thanks in advance!
One way I've gotten around this is by conditionally rendering a skeleton if it is still loading and then actually rendering the details once finished.
I'm not sure if this is exactly the solution you're looking for (and you may already know it's an option), but maybe this helps?
Using firebase you must create a listener.
do something like this:
Reducer Action:
// Action Creators
export function onAuthChanged(fb) {
return async (dispatch) => {
fb.auth().onAuthStateChanged((res) => {
dispatch({
type: Types.SET_ATTR,
payload: {
attr: 'user',
value: res,
},
});
});
};
}
call this function from a FirebaseProvider componentWillMount function
then
put the FirebaseProvider on your App class;
const App = () => {
return (
<>
<Provider store={store}>
<TranslatorProvider />
<FirebaseProvider />
<ThemeProvider>
<TrackingProvider>
<Routes />
</TrackingProvider>
</ThemeProvider>
</Provider>
</>
);
};
the listener will save on your reducer the user when it login and logout
According to what you have provided, definitely there will be a delay. I'll explain what is happening here.
You are requesting data from the firebase after you have rendered the details on Profile. This happens because you are requesting data in componentDidMount method. This method gets called first time Render method is completely finished rendering your components. So I'll suggest you two methods to get rid of that.
As Coding Duck suggested, you can show a skeleton loader until you fetch data from the firebase.
You can request these data from your login. That means, if user authentication is success, you can request these data using fetchProfile action and once you fetch these data completely, you can use Navigation.navigate('Profile') to navigate to your Profile screen rather than directly navigate to it once the authentication is success. In that time since you have fetched data already, there will be no issue.
Also you can use firebase persist option to locally store these data. So even if there were no internet connection, still firebase will provide your profile information rapidly.
EDIT
More specific answer with some random class and function names. This is just an example.
Let's say onLogin function handles all your login requirements in your authentication class.
onLogin = () => {
/** Do the validation and let's assume validation is success */
/** Now you are not directly navigating to your Profile page like you did in the GIF. I assume that's what you did because you have not added more code samples to fully understand what you have done.*/
/** So now you are calling the fetchProfile action through props and retrieve your details. */
this.props.fetchProfile(this.props.navigation);
};
Now let's modify your fetchDetails action.
export const fetchProfile = (navigation) => {
const {currentUser} = firebase.auth();
return (dispatch) => {
firebase
.database()
.ref(`/users/${currentUser.uid}/profile`)
.on('value', (snapshot) => {
dispatch({
type: PROFILE_FETCH,
payload: snapshot.val(),
});
navigation.navigate('Profile')
});
};
};
Note : This is not the best method of handling navigations but use a global navigation service to access directly top level navigator. You can learn more about that in React Navigation Documentation. But let's use that for now in this example.
So as you can see, when user login is successful, now you are not requesting data after rendering the Profile page but request data even before navigating to the page. So this ensures that profile page is only getting loaded with relevant data and there will be no lag like in your GIF.

Is there a way to set a font globally in React Native?

I need to create a custom font that applies to every Text component in the whole application.
Is there is a way to set a font globally in React Native?
One way is to create a wrapper for RN Text say MyTextCustomFont:
const MyTextCustomFont = (props) => {
return (
<Text style={{fontFamily:'myFont'}} {...props} >{props.children}</Text>
)
}
import this MyTextCustomFont and use anywhere.
Another way is to define a style object and use it wherever you want.
To do this we have to implement a method in which we will override Text component creation in React Native. In this we will set default font family or size or any attribute we want to set by default.
// typography.js
import React from 'react'
import { Text, Platform, StyleSheet } from 'react-native'
export const typography = () => {
const oldTextRender = Text.render
Text.render = function(...args) {
const origin = oldTextRender.call(this, ...args)
return React.cloneElement(origin, {
style: [styles.defaultText, origin.props.style],
})
}
}
const styles = StyleSheet.create({
defaultText: {
fontFamily: 'NunitoSans-Regular',//Default font family
}
});
Then in index.js you have to do this:
import { typography } from './src/utils/typography'
typography()
Detailed answer here:
https://ospfolio.com/two-way-to-change-default-font-family-in-react-native/
I think your problem is add Custom Fonts in react native.
1. Add Your Custom Fonts to Assets
Add all the font files you want to an “assets/fonts” folder in the root of your react native project:
2. Edit Package.json
Adding rnpm to package.json providing the path to the font files:
"rnpm": {
"assets": [
"./assets/fonts/"
]
},
3. Link assest files
run this command in your react native project root folder
react-native link
This should add the font references in your Info.plist file for iOS and on Android copy the font files to android/app/src/main/assets/fonts.
4. Add in stylesheet
Add a fontFamily property with your font name:
const styles = StyleSheet.create({
title: {
fontSize: 16,
fontFamily: 'PlayfairDisplay-Bold',
color: '#fff',
paddingRight: 20,
},
});
So, I've made a component doing this quite easely some times ago. This is working with Expo, I don't know for vanilla react-native.
at the start of your app:
import { Font, Asset } from 'expo'
async initFont() {
try {
await Font.loadAsync({
'Bariol': require('src/assets/font/Bariol_Regular.otf'),
'Bariol Bold': require('src/assets/font/Bariol_Bold.otf'),
})
this.setState({ fontReady: true })
} catch (e) {
console.log(e)
}
}
Then, you have to create a component file like text.js containing this code:
export default function (props) {
let font = { fontFamily: 'Bariol' }
if (props.bold) {
font = { fontFamily: 'Bariol Bold' }
}
const { bold, style, children, ...newProps } = props
return (
<Text {...newProps} style={[Style.text, props.style, font]}>
{props.children}
</Text>
)
}
Finally, in any of you other component / page just import MyText:
import Text from 'path/to/text.js'
use it like a normal Text component:
<Text bold>Hello World!</Text>
Even if this solution looks a bit more complicated than the others, it is easier to use once the setup is ok, since you just have to import Text.
You can override Text behaviour by adding this in any of your component using Text:
Edit: Add this code in your App.js or main file
let oldRender = Text.render;
Text.render = function (...args) {
let origin = oldRender.call(this, ...args);
return React.cloneElement(origin, {
style: [{color: 'red', fontFamily: 'Arial'}, origin.props.style]
});
}
For react Native Version 0.56 or below, Add this code in your App.js or main file
let oldRender = Text.prototype.render;
Text.prototype.render = function (...args) {
let origin = oldRender.call(this, ...args);
return React.cloneElement(origin, {
style: [{color: 'red', fontFamily: 'Arial'}, origin.props.style]
});
};
Reference
Or create your own component, such as MyAppText.
MyAppText would be a simple component that renders a Text component using your universal style and can pass through other props, etc.
I use a wrapper with default props like this :
const CustomText = ({ fontFam = "regular", ...props }) => {
const typo = {
light: "Montserrat_300Light",
regular: "Montserrat_400Regular",
bold: "Montserrat_600SemiBold",
};
return (
<Text {...props} style={[{ fontFamily: typo[fontFam], ...props.style }]}>
{props.children}
</Text>
);
};
export default CustomText;
By default, if "fontFam" is not indicated it will be regular font.
An example with bold typo :
<CustomText fontFam="bold" style={{ marginTop: 30, color: "grey" }}>
Some Text
</CustomText>
You can replace all your <Text/> by <CustomText />.
If you don't have to create custom component, you could try react-native-global-font. It will be apply for your all Text and TextInput
yes
app.js
import styles from './styles';
{...}
<Text style={styles.text}>hello World </Text>
{...}
styles.js
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
text: {
// define your font or size or whatever you want to style here
},
use style on every text and all changes will affect all text components

ReduxForm vs tcomb-form for Native Development

We used ReduxForm in our Web App and we are now trying to build a native app and it seems most of the guys are using tcomb-form.
ReduxForm can be used for native development. Not sure what's the biggest advantage of using tcomb-form.
I tried using it (Didn't explore all the advanced features yet) how ever once we define the schema the basic rendering of the controls is done for you. How ever if you want to customize how a control is displayed i am not sure whether tcomb-form supports it.
Ex:
Here in my Component's render() method:
let Form= tFormNative.form.Form;
let options= {
fields: {
}
};
let username= {
label: I18n.t('LoginForm.username'),
maxLength: 12,
editable: true,
};
let email = {
label: I18n.t('LoginForm.email'),
keyboardType: 'email-address',
editable: true,
};
let password = {
label: I18n.t('LoginForm.password'),
maxLength: 12,
secureTextEntry: true,
editable: true,
};
let registerForm = tFormNative.struct({
username: tFormNative.String,
email: tFormNative.String,
password: tFormNative.String,
passwordReEnter: tFormNative.String,
});
return(
<View style={styles.container}>
<Form
style={styles.textInput}
ref='form'
type={registerForm}
options={options}
/>
</View>
);
Now the Label and Control ( based on the type you provided in struct() ) are created.
How ever Lets say i want to use an Icon aling with the Label for each control i am not sure whether that is permitted.
Appreciate any inputs.
Thanks
Sateesh
If you would like to customize the entire control you can go for a factory, I'll post an example of a Slider i use for number input. I have not tried ReduxForm but i do like tcomb-form a lot, and I don't see anything that should not be doable in terms of customization. Good luck to you!
import React from 'react';
import { View, Text, Slider } from 'react-native';
import t from 'tcomb-form-native';
import Strings from '../config/strings.js';
var Component = t.form.Component;
class TcombSlider extends Component {
constructor(props) {
super(props);
var locals = super.getLocals();
}
getLocals() {
var locals = super.getLocals();
return locals;
}
getTemplate() {
let self = this;
return function (locals) {
var sliderConfig = locals.config.slider;
var stylesheet = locals.stylesheet;
var formGroupStyle = stylesheet.formGroup.normal;
var controlLabelStyle = stylesheet.controlLabel.normal;
var checkboxStyle = stylesheet.checkbox.normal;
var helpBlockStyle = stylesheet.helpBlock.normal;
var errorBlockStyle = stylesheet.errorBlock;
if (locals.hasError) {
formGroupStyle = stylesheet.formGroup.error;
controlLabelStyle = stylesheet.controlLabel.error;
checkboxStyle = stylesheet.checkbox.error;
helpBlockStyle = stylesheet.helpBlock.error;
}
var label = locals.label ? <Text style={controlLabelStyle}>{locals.label}</Text> : null;
var help = locals.config.help ? <Text style={helpBlockStyle}>{locals.config.help}</Text> : null;
var error = locals.hasError && locals.error ? <Text accessibilityLiveRegion="polite" style={[errorBlockStyle, {marginTop: 2}]}>{locals.error}</Text> : null;
return (
<View style={formGroupStyle}>
{label}
<View style={{flex: 1, flexDirection: 'row', justifyContent: 'flex-end', paddingRight: 15}}>
<Text>{locals.value}m</Text>
</View>
<View style={{marginBottom: 5}}>
<Slider
minimumValue={sliderConfig.minimumValue}
maximumValue={sliderConfig.maximumValue}
step={sliderConfig.step}
value={locals.value}
onValueChange={(value) => self._onChange(locals, value)}/>
</View>
{help}
{error}
</View>
);
}
}
_onChange(locals, val) {
locals.onChange(val);
}
}
export default TcombSlider
And use it like this:
const options = {
fields: {
search_range: {
config: {
slider: {
maximumValue: 1000,
minimumValue: 0,
step: 5,
disabled: false,
},
},
factory: TcombSlider,
},
...
},
}
I'm posting this as an example because i had a hard time putting together my first factory.
I would leave this as a comment if I could, but my reputation isn't high enough.
You need to override your form's local template with a custom template which adds an Icon and TextInput for each field.
This gist shows how you can do this.

Cyclic dependency returns empty object in React Native

I have two React Native Components (Alpha and Beta) that navigate to one another; however, this produces a cyclic dependency and React Native doesn’t seem to handle those.
Requiring Beta in Alpha works fine, but requiring Alpha in Beta returns an empty object. An error is thrown when trying to push a route with an invalid component.
Can cyclic dependencies work in React Native? If not, how do I get around this?
Code
index.ios.js
'use strict';
var React = require('react-native');
var Alpha = require('./Alpha');
var {
AppRegistry,
NavigatorIOS,
StyleSheet,
Text,
View,
} = React;
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
}} />
);
},
});
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white'
},
wrapper: {
paddingTop: 64
}
});
AppRegistry.registerComponent('ExampleProject', () => ExampleProject);
Alpha.js
'use strict';
var React = require('react-native');
var Beta = require('./Beta');
var {
StyleSheet,
TouchableHighlight,
View,
Text
} = React;
var Alpha = React.createClass({
statics: {
title: 'Alpha'
},
goToBeta() {
this.props.navigator.push({
component: Beta,
title: Beta.title,
wrapperStyle: styles.wrapper
});
},
render() {
return (
<View>
<TouchableHighlight style={styles.linkWrap}
onPress={this.goToBeta}>
<Text>Go to Beta</Text>
</TouchableHighlight>
</View>
);
}
});
var styles = StyleSheet.create({
linkWrap: {
flex: 1,
alignItems: 'center',
padding: 30
},
wrapper: {
paddingTop: 64
}
});
module.exports = Alpha;
Beta.js
'use strict';
var React = require('react-native');
var Alpha = require('./Alpha');
var {
StyleSheet,
TouchableHighlight,
View,
Text
} = React;
var Beta = React.createClass({
statics: {
title: 'Beta'
},
goToAlpha() {
this.props.navigator.push({
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
});
},
render() {
return (
<View>
<TouchableHighlight style={styles.linkWrap}
onPress={this.goToAlpha}>
<Text>Go to Alpha</Text>
</TouchableHighlight>
</View>
);
}
});
var styles = StyleSheet.create({
linkWrap: {
flex: 1,
alignItems: 'center',
padding: 30
},
wrapper: {
paddingTop: 64
}
});
module.exports = Beta;
This is a common problem with routing components. There are a couple of ways to approach this. In general, you want Alpha to avoid requiring Beta before Alpha has defined its exports and vice versa.
Fortunately, JavaScript's import and export keywords address this issue by lazily importing values using an intermediate object that acts as a level of indirection. A specific example is a lot easier to understand.
With import and export
Use the export keyword to export Alpha and Beta from their respective files, and use the import keyword to import them:
// Alpha.js
import Beta from './Beta';
var Alpha = React.createClass({
/* ... */
});
export default Alpha;
This works because at runtime, the export keyword creates an intermediate object (equivalent to module.exports in CommonJS) and assigns a property named default to it with the value of Alpha. So, the above export statement is conceptually similar to this:
module.exports.default = Alpha;
The files that import Alpha then get a reference to the intermediate object, not Alpha itself until Alpha is directly used. So this code here actually lazily accesses Alpha:
import Alpha from './Alpha';
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: Alpha,
title: Alpha.title,
wrapperStyle: styles.wrapper
}}
/>
);
},
});
The lazy access is implemented by running code conceptually similar to this:
var AlphaExports = require('./Alpha');
var ExampleProject = React.createClass({
render() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: AlphaExports.default,
title: AlphaExports.default.title,
wrapperStyle: styles.wrapper
}}
/>
);
},
});
With require()
Using CommonJS's require, you have some other options:
Approach 1: Lazy Loading
Alpha and Beta don't need each other until the user navigates from one scene to the next. For the app to reach this state, Alpha must have defined its exports (that is, module.exports = Alpha must have run for your app to have rendered an Alpha component). So, when the user is navigating to the scene displaying Beta, it is safe for Beta to require Alpha and therefore it is safe to require Beta at this point in time.
// Alpha.js
var Alpha = React.createClass({
goToBeta() {
// Lazily require Beta, waiting until Alpha has been initialized
var Beta = require('./Beta');
this.props.navigator.push({
component: Beta,
title: Beta.title,
wrapperStyle: styles.wrapper
});
}
});
Although it isn't necessary to do the same for Beta.js in this specific scenario because Alpha is the first component loaded, it's probably a good idea so that your components all handle dependency cycles the same way.
Approach 2: Single Module
Another solution is to put Alpha and Beta in the same JS file to remove a cycle between modules. You would then export both components from the new mega-module.
// AlphaBeta.js
var Alpha = React.createClass({...});
var Beta = React.createClass({...});
exports.Alpha = Alpha;
exports.Beta = Beta;
To require it:
// index.js
var {Alpha, Beta} = require('./AlphaBeta');