Implementing dark mode in React Native sensibly - react-native

I'm trying to add dark mode support to my React Native app. I will have a flag in a mobx store mode which will be light or dark as appropriate.
In order to tie this into an existing app, I wanted to, if possible, keep the existing style definitions and only override when needed (rather than rewrite everything to a light and a dark theme).
I came up with a function like the following to return the appropriate styles based on the current mode:
function getStyle(style) {
let ret = [styles[style]];
if (
styles.hasOwnProperty(Store.mode) &&
styles[Store.mode][style] !== "undefined"
) {
ret.push(styles[Store.mode][style]);
}
return ret;
}
The view would be rendered as such:
...
<View style={getStyle("container")}>
<Text style={getStyle("text")}>Some text</Text>
</View>
...
The styles:
const styles = {
dark: {
container: {
backgroundColor: "#000"
},
text: {
color: "#fff"
}
},
container: {
padding: 20,
backgroundColor: "#fff"
},
text: {
fontSize: 18,
color: "#000"
}
};
Now this works, but I'm not sure if it's coming at some performance cost I'm unaware of right now (the use of the function, using a style object instead of StyleSheet.create...), or if there's a much simpler way I can't see for the trees. I'd rather not do a ternary inline on every element either.

I ended up going a slightly different way, in that I'd add extra styles depending on the current mode, e.g.
<View style={[styles.container, theme[Store.mode].container]}>
<Text style={[styles.text, theme[Store.mode].text]}>Some text</Text>
</View>
And then using the theme var to override
const theme = {
light: {},
dark: {
container: {
backgroundColor: "#000"
},
text: {
color: "#fff"
}
}
};
const styles = {
container: {
padding: 20,
backgroundColor: "#fff"
},
text: {
fontSize: 18,
color: "#000"
}
};

I would suggest taking a look at the Context api in ReactJS. It gives a good out of the box solution for maintaining global data around the component tree.

You can use React.createContext() and react-native-paper
This module makes it simple to change the background with just a button. I made a simple example for you.
I made an example link.

Related

Using React Native StyleSheet like class of HTML

I had this question a long time ago. What if I use React Native StyleSheet like class of HTML? I really did use it. I have a lot of styles (names are based on Bootstrap Class Name).
import { StyleSheet } from "react-native";
const s = StyleSheet.create({
flex: { flex: 1 },
flexCenter: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingVertical: 24,
},
p0: { padding: 0 },
p1: { padding: 4 },
p2: { padding: 8 },
textLight: { color: "#eee" },
fsLg: { fontSize: 18 },
... and so on
});
export default s;
Then, in components I use it like this.
<Text style={[s.fsLg, s.p2, s.textLight]}>Title</Text>
I do not use it anymore because I had a feeling it was a bad practice. I don't know surely. I have read official react native styling. They did not mention this is bad. How do anyone think?
When in doubt, check the docs: https://reactnative.dev/docs/style
The style prop can be a plain old JavaScript object. That's what we usually use for example code. You can also pass an array of styles - the last style in the array has precedence, so you can use this to inherit styles.
From the second paragraph. This is a recommended practice.

Inheritance In React Native Style Sheets

So in a regular cascading style sheet we can inherit from other styles doing so:
.myStyle .temp {
height: 100px,
width: 80px,
}
My question is:
is there a way for me to do this in react native. I have tried a few different ways but cannot seem to get it to work. Some of the methods in which I have tried are as follows:
// Example One
// Error Thrown: tempStyle is not defined.
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
...tempStyle,
},
tempStyle :{
backgroundColor: 'tomato'
}
});
The following also did not work for me, it did not throw any errors but simply did not work.
I set the style to be the tempStyle and it was basically a blank style sheet which does make sense as it probably points to nothing.
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
tempStyle :{
container :{
backgroundColor: 'tomato'
}
}
});
I know that we can reference multiple styles inside of the style property in a component using brackets.
<View style={[Styles.temp, Styles.tempTwo]} />
Is this the only way to accomplish this?
Your first idea does not work because you are trying to use a style object defined inside the create function of StyleSheet for another style object defined within the same create function. You cannot access them inside the create function.
However, you could define your styles in a plain JS object and then use the spread syntax in order to achieve pretty much the same thing.
Notice that a style is just a JS object after all.
Let us call this file styles.js.
// helper, keep local
const tempStyle = {
backgroundColor: 'tomato'
}
// we only want to use this style
// spread syntax works here
export const container = {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
...tempStyle,
}
export SomeOtherStyle = {
...tempStyle,
}
Then, we can use the above styles as usual.
import { container, SomeOtherStyle } from './styles.js'
...
<View style={[container, someOtherStyle]}></View>
Notice that we can make use of typescript here as well (if you are using it in your project).
// helper, keep local
const tempStyle: ViewStyle = {
backgroundColor: 'tomato'
}
// we only want to use this style
// spread syntax works here
export const container: ViewStyle = {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
...tempStyle,
}
export SomeOtherStyle: ViewStyle = {
...tempStyle,
}
Using typescript, you will have autocompletion and typechecking for your styles. Notice as well that there might be different styles for different types of components (e.g. TextStyle).
you can use the array notation:
<View style={[styles.container, styles.tempStyle]} />

Is it possible to change the style of selected date of React Native calendar?

I am making a React Native app in which calendars in it.
Is it possible to change the style of selected date of React Native calendar?
As shown in the image, I want to change the style of (6/10/2019) like the image.
Is there any way to change this? Or which module should I use for this?
Those are modules that I know related with calendar.
react-native-calendars
react-native-calendar-events
react-native-calendar
react-native-calendar-strip
react-native-bpk-component-calendar
react-native-calendar-select
react-native-calendario
react-native-dates
react-native-event-calendar-customized
react-native-calendar-reminders
Yes! Here's an example
<Calendar
// Date marking style [simple/period/multi-dot/single]. Default = 'simple'
markingType={'custom'}
markedDates={{
'2018-03-28': {
customStyles: {
container: {
backgroundColor: 'green'
},
text: {
color: 'black',
fontWeight: 'bold'
}
}
},
'2018-03-29': {
customStyles: {
container: {
backgroundColor: 'white',
elevation: 2
},
text: {
color: 'blue'
}
}
}
}}
/>

How to set background color of view transparent in React Native

This is the style of the view that i have used
backCover: {
position: 'absolute',
marginTop: 20,
top: 0,
bottom: 0,
left: 0,
right: 0,
}
Currently it has a white background. I can change the backgroundColor as i want like '#343434' but it accepts only max 6 hexvalue for color so I cannot give opacity on that like '#00ffffff'. I tried using opacity like this
backCover: {
position: 'absolute',
marginTop: 20,
top: 0,
bottom: 0,
left: 0,
right: 0,
opacity: 0.5,
}
but it reduces visibility of view's content.
So any answers?
Use rgba value for the backgroundColor.
For example,
backgroundColor: 'rgba(52, 52, 52, 0.8)'
This sets it to a grey color with 80% opacity, which is derived from the opacity decimal, 0.8. This value can be anything from 0.0 to 1.0.
The following works fine:
backgroundColor: 'rgba(52, 52, 52, alpha)'
You could also try:
backgroundColor: 'transparent'
Try this backgroundColor: '#00000000'
it will set background color to transparent, it follows #rrggbbaa hex codes
Surprisingly no one told about this, which provides some !clarity:
style={{
backgroundColor: 'white',
opacity: 0.7
}}
Try to use transparent attribute value for making transparent background color.
backgroundColor: 'transparent'
You should be aware of the current conflicts that exists with iOS and RGBA backgrounds.
Summary: public React Native currently exposes the iOS layer shadow
properties more-or-less directly, however there are a number of
problems with this:
1) Performance when using these properties is poor by default. That's
because iOS calculates the shadow by getting the exact pixel mask of
the view, including any tranlucent content, and all of its subviews,
which is very CPU and GPU-intensive. 2) The iOS shadow properties do
not match the syntax or semantics of the CSS box-shadow standard, and
are unlikely to be possible to implement on Android. 3) We don't
expose the layer.shadowPath property, which is crucial to getting
good performance out of layer shadows.
This diff solves problem number 1) by implementing a default
shadowPath that matches the view border for views with an opaque
background. This improves the performance of shadows by optimizing for
the common usage case. I've also reinstated background color
propagation for views which have shadow props - this should help
ensure that this best-case scenario occurs more often.
For views with an explicit transparent background, the shadow will
continue to work as it did before ( shadowPath will be left unset,
and the shadow will be derived exactly from the pixels of the view and
its subviews). This is the worst-case path for performance, however,
so you should avoid it unless absolutely necessary. Support for this
may be disabled by default in future, or dropped altogether.
For translucent images, it is suggested that you bake the shadow into
the image itself, or use another mechanism to pre-generate the shadow.
For text shadows, you should use the textShadow properties, which work
cross-platform and have much better performance.
Problem number 2) will be solved in a future diff, possibly by
renaming the iOS shadowXXX properties to boxShadowXXX, and changing
the syntax and semantics to match the CSS standards.
Problem number 3) is now mostly moot, since we generate the shadowPath
automatically. In future, we may provide an iOS-specific prop to set
the path explicitly if there's a demand for more precise control of
the shadow.
Reviewed By: weicool
Commit: https://github.com/facebook/react-native/commit/e4c53c28aea7e067e48f5c8c0100c7cafc031b06
Adding reference of React-Native Version 0.64
Named colors
Named Colors: DOCS
In React Native you can also use color name strings as values.
Note: React Native only supports lowercase color names. Uppercase color names are not supported.
transparent#
This is a shortcut for rgba(0,0,0,0), same like in CSS3.
Hence you can do this:
background: {
backgroundColor: 'transparent'
},
Which is a shortcut of :
background: {
backgroundColor: 'rgba(0,0,0,0)'
},
In case you have hex color, you can convert it to rgba and set the opacity there:
const hexToRgbA = (hex, opacity) => {
let c;
if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
c = hex.substring(1).split('');
if (c.length === 3) {
c = [c[0], c[0], c[1], c[1], c[2], c[2]];
}
c = `0x${c.join('')}`;
return `rgba(${[(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',')},${opacity})`;
}
throw new Error('Bad Hex');
};
const color = '#1f8b7f'; // could be a variable
return (
<View style={{ backgroundColor: hexToRgbA(color, 0.1) }} />
)
source that helped me
This will do the trick help you,
Add one View element and add style as below to that view
.opaque{
position:'absolute',
backgroundColor: 'black',
opacity: 0.7,
zIndex:0
}
The best way to use background is hex code #rrggbbaa but it should be in hex.
Eg: 50% opacity means 256/2 =128, then convert that value(128) in HEX that will be 80,use #00000080 80 here means 50% transparent.
Here is my solution to a modal that can be rendered on any screen and initialized in App.tsx
ModalComponent.tsx
import React, { Component } from 'react';
import { Modal, Text, TouchableHighlight, View, StyleSheet, Platform } from 'react-native';
import EventEmitter from 'events';
// I keep localization files for strings and device metrics like height and width which are used for styling
import strings from '../../config/strings';
import metrics from '../../config/metrics';
const emitter = new EventEmitter();
export const _modalEmitter = emitter
export class ModalView extends Component {
state: {
modalVisible: boolean,
text: string,
callbackSubmit: any,
callbackCancel: any,
animation: any
}
constructor(props) {
super(props)
this.state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {}),
animation: new Animated.Value(0)
}
}
componentDidMount() {
_modalEmitter.addListener(strings.modalOpen, (event) => {
var state = {
modalVisible: true,
text: event.text,
callbackSubmit: event.onSubmit,
callbackCancel: event.onClose,
animation: new Animated.Value(0)
}
this.setState(state)
})
_modalEmitter.addListener(strings.modalClose, (event) => {
var state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {}),
animation: new Animated.Value(0)
}
this.setState(state)
})
}
componentWillUnmount() {
var state = {
modalVisible: false,
text: "",
callbackSubmit: (() => {}),
callbackCancel: (() => {})
}
this.setState(state)
}
closeModal = () => {
_modalEmitter.emit(strings.modalClose)
}
startAnimation=()=>{
Animated.timing(this.state.animation, {
toValue : 0.5,
duration : 500
}).start()
}
body = () => {
const animatedOpacity ={
opacity : this.state.animation
}
this.startAnimation()
return (
<View style={{ height: 0 }}>
<Modal
animationType="fade"
transparent={true}
visible={this.state.modalVisible}>
// render a transparent gray background over the whole screen and animate it to fade in, touchable opacity to close modal on click out
<Animated.View style={[styles.modalBackground, animatedOpacity]} >
<TouchableOpacity onPress={() => this.closeModal()} activeOpacity={1} style={[styles.modalBackground, {opacity: 1} ]} >
</TouchableOpacity>
</Animated.View>
// render an absolutely positioned modal component over that background
<View style={styles.modalContent}>
<View key="text_container">
<Text>{this.state.text}?</Text>
</View>
<View key="options_container">
// keep in mind the content styling is very minimal for this example, you can put in your own component here or style and make it behave as you wish
<TouchableOpacity
onPress={() => {
this.state.callbackSubmit();
}}>
<Text>Confirm</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
this.state.callbackCancel();
}}>
<Text>Cancel</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
</View>
);
}
render() {
return this.body()
}
}
// to center the modal on your screen
// top: metrics.DEVICE_HEIGHT/2 positions the top of the modal at the center of your screen
// however you wanna consider your modal's height and subtract half of that so that the
// center of the modal is centered not the top, additionally for 'ios' taking into consideration
// the 20px top bunny ears offset hence - (Platform.OS == 'ios'? 120 : 100)
// where 100 is half of the modal's height of 200
const styles = StyleSheet.create({
modalBackground: {
height: '100%',
width: '100%',
backgroundColor: 'gray',
zIndex: -1
},
modalContent: {
position: 'absolute',
alignSelf: 'center',
zIndex: 1,
top: metrics.DEVICE_HEIGHT/2 - (Platform.OS == 'ios'? 120 : 100),
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
height: 200,
width: '80%',
borderRadius: 27,
backgroundColor: 'white',
opacity: 1
},
})
App.tsx render and import
import { ModalView } from './{your_path}/ModalComponent';
render() {
return (
<React.Fragment>
<StatusBar barStyle={'dark-content'} />
<AppRouter />
<ModalView />
</React.Fragment>
)
}
and to use it from any component
SomeComponent.tsx
import { _modalEmitter } from './{your_path}/ModalComponent'
// Some functions within your component
showModal(modalText, callbackOnSubmit, callbackOnClose) {
_modalEmitter.emit(strings.modalOpen, { text: modalText, onSubmit: callbackOnSubmit.bind(this), onClose: callbackOnClose.bind(this) })
}
closeModal() {
_modalEmitter.emit(strings.modalClose)
}
Hope I was able to help some of you, I used a very similar structure for in-app notifications
Happy coding

Odd react-native issue with rendering

I'm currently trying to learn react-native and running into a problem after a tutorial. I followed the PropertyFinder tutorial on Ray Wenderlich's site, which went fine.
Breaking away now to start something on my own, I can't seem to get a page to render - and I keep running into the same exception which may be causing the page not to render (but I'm not entirely sure).
Here is the main app page, which is simply the navigator:
'use strict';
var React = require('react-native');
var WelcomePage = require('./WelcomePage');
var {
AppRegistry,
StyleSheet,
Text,
View,
} = React;
var MyApp = React.createClass({
render: function() {
return (
<React.NavigatorIOS style={styles.container}
initialRoute={{
title: 'Welcome',
component: WelcomePage
}}/>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
AppRegistry.registerComponent('MyApp', () => MyApp);
And here's the welcome page:
'use strict';
var React = require('react-native');
var {
StyleSheet,
Text,
View,
Component
} = React;
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
textTitle: {
color: 'black',
fontSize: 35,
textAlign: 'center'
},
textSubs: {
color: 'black',
fontSize: 25,
textAlign: 'center'
}
});
class WelcomePage extends Component {
render() {
console.log('About to render welcome page');
return (
<View style={styles.container}>
<Text style={styles.textTitle}>
Welcome to MyApp!
</Text>
<Text style={styles.textSubs}>
Good Choice for an App
</Text>
<Text style={styles.textSubs}>
Going to start with setting up the App
</Text>
<Text style={styles.textSubs}>
Are you ready?
</Text>
</View>
);
}
}
module.exports = WelcomePage;
Now, the console logging comes out correctly with 'About to render welcome page', however the result I get is just a blank white window. If I use the chrome debugger, I do get an exception if a chose to Pause on Exceptions. The code snippet it pauses on is this:
/**
* Given a constructor can we call it without `new`?
*
* #param {function} Collection
*/
function isCallableWithoutNew(Collection) {
try {
Collection(); // <<<--- Here's where the exception seems to happen
} catch (e) {
return false;
}
return true;
}
If I go back to the tutorial project, it works just fine.
The only difference I can see is that when I initialized the tutorial project, the Animation library I pulled in did not have an 'Experimental' version (RCTAnimation), but with the new project I tried to start above, I have to use the Experimental version if I want it to work. Really though, I only tested that early on and am not pulling it in any longer (I don't need it yet).
I've also tried playing around a bit with the styles, but get the same result - and the same exception occurring.
Exception stack chrome shows is:
"TypeError: Constructor Map requires 'new'
at Map (native)
at isCallableWithoutNew (http://localhost:8081/index.ios.bundle:15407:5)
at shouldPolyfillES6Collection (http://localhost:8081/index.ios.bundle:15378:5)
at http://localhost:8081/index.ios.bundle:14500:8
at http://localhost:8081/index.ios.bundle:15095:3
at require (http://localhost:8081/index.ios.bundle:245:25)
at http://localhost:8081/index.ios.bundle:14288:11
at require (http://localhost:8081/index.ios.bundle:245:25)
at http://localhost:8081/index.ios.bundle:14131:11
at require (http://localhost:8081/index.ios.bundle:245:25)"
I want to say that looks like an API version issue or something?
Am I missing something obvious here?
Thanks for any help.
So this ended up being my styles on the 'index.ios.js' page.
I changed this:
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
To this:
var styles = StyleSheet.create({
container: {
flex: 1
}
});
And the view then rendered.
I guess I need to spend some time on figuring out how these styles layout components...
If anyone knows why the main NavigatorIOS pane would push content too far off to the right to be visible (which is what was happening) if the styles were set as they were originally, please let me know.
The culprits were:
'justifyContent'
'alignItems'
'backgroundColor' of course had no affect on the view rendering (I didn't add it back in but could without issue).