React Native Styling Precedence - react-native

This question is about using an array arg for the styling prop on a react component:
style={[styles.localTextStyle, textStyle...]}
As I understand precedence is from last element to first. So in the example above textStyle would overwrite styles.localTextStyle. This is good, however, I am making a custom component and I want to be able to specify inline props from the parent and have the inline be of highest precedence yet not overwrite previous styles if no prop is provided.
For example, if I were writing a custom component called Text:
<Text style={[styles.localTextStyle, textStyle, {
color: color
}]}>
I would use localTextStyle as defaults then styling passed from parent called textStyle, and finally the prop called color to set the color. This only works when the prop color is defined, otherwise, it will overwrite color to unset despite it possibly being set in textStyle for earlier styling.
So I'm wondering what the best way to circumvent this is. I currently have wrapped the final arg in a function called Clean which returns a new object with only defined keys-values. That works but it makes the code messy and I'd be shocked if someone didn't have a smarter, better way to do this.
<Text style={[styles.localTextStyle, textStyle, Clean({
color: color
})]}>

It's written inside the Documentation that :
You can also pass an array of styles - the last style in the array has precedence, so you can use this to inherit styles.
And I checked and It's like that :
[Component-Style] < [inside-array] < [outside-the-array]
like this :
TSButton has it's own styling in it's declaration
<TSButton style={[Style.button,{padding:0,width:50,height:20}]} />
So here the priority is as >>
TSButton Styling < Style.button < padding , width, ...

Related

Simplified style change onPress React Native

The following is a first attempt at learning to simply change the style of an element onPress in react native. Being well versed in web languages I am finding it difficult as it is not as straight forward.
For reasons as yet unknown, the element requires two clicks in order to execute.
export class NavTabItem extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
}
this.NavTabAction = this.NavTabAction.bind(this)
}
NavTabAction = (elem) => {
elem.setState({active: !elem.state.active})
}
render() {
return (
<TouchableOpacity
style={this.state.active ? styles.NavTabItemSelected : styles.NavTabItem}
onPress={()=> {
this.NavTabAction(this)
}}>
<View style={styles.NavTabIcon} />
<Text style={styles.NavTabLabel}>{this.props.children}</Text>
</TouchableOpacity>
);
}
}
Other issues:
I also have not worked out how a means of setting the active state to false for other elements under the parent on click.
Additionally, Is there a simple way to affect the style of child elements like with the web. At the moment I cannot see a means of a parent style affecting a child element through selectors like you can with CSS
eg. a stylesheet that read NavTabItemSelected Text :{ // active style for <Text> }
Instead of calling elem.setState or elem.state, it should be this.setState and elem.state.
NavTabAction = (elem) => {
this.setState(prev => ({...prev, active: !prev.active}))
}
And instead of passing this in the onPress, you should just pass the function's reference.
onPress={this.NavTabAction}>
You should also remove this line because you are using arrow function
// no need to bind when using arrow functions
this.NavTabAction = this.NavTabAction.bind(this)
Additionally, Is there a simple way to affect the style of child elements like with the web
You could check styled-component, but I think that feature don't exists yet for react native. What you should do is pass props down to child components.
Thanks to everyone for their help with this and sorting out some other bits and pieces with the code.
The issue in question however was that the style was changing on the second click. A few hours later and I have a cause and a solution for anyone suffering from this. Should any of the far more experienced people who have answered this question believe this answer is incorrect or they have a better one, please post it but for now here is the only way I have found to fix it.
The cause:
Using setState was correctly re rendering the variables. This could both be seen in the console via console.log() and directly outputted in the render making them visible.
However, no matter what was tried, this did not update the style. Whether it was a style name from the Stylesheet or inline styles, they would update on the second click rather than the first but still to the parameters of the first. So if the first click should make a button turn from red to green, it would not do so even though the new state had rendered. However if a subsequent click should have turned the button back to red then the button would now go green (like it should have for the first click). It would then go red on the third click seemingly always one step behind the status passed to it.
Solution
To fix this, take the style off the the primary element (forgive terminology, someone edit), in my case, the TouchableOpacity element. Add in a child View element and place the styles on that View element instead along with the ternary operator and wallah.
It seems any change to status on the effective master element or container if you prefer, only takes affect after another render, not that contained in setStatus.
Final code:
export class NavTabItem extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
}
}
NavTabAction = () => {
this.setState({active: !this.state.active})
}
render() {
this.state.active == true ? console.log("selected") : console.log("unselected")
return (
<TouchableOpacity onPress={this.NavTabAction}>
// added View containing style and ternary operator
<View style={this.state.active == true ? styles.NavTabItemSelected : styles.NavTabItem}>
<View style={styles.NavTabIcon} />
<TextCap11 style={styles.NavTabLabel}>{this.props.children}</TextCap11>
</View>
// End added view
</TouchableOpacity>
);
}
}

The purpose of StyleSheet.create in React Native

I wanted to ask the community about the changes in StyleSheet.create in React Native.
Before:
I have reviewed the past questions about this topic, such as this question, but they all have been answered pretty a long time ago (apart from this answer, but I wanted to have something definite) and a lot has changed since.
Before StyleSheet was creating a unique id for the styles, mainly for performance optimisations. If you wanted to get the styles out of the created styles object, you should have used the flatten method. The majority of the answers reference this flatten method and you could not access styles property as if it was a normal object.
E.g.
const styles = StyleSheet.create({
modalContainer: {
width: 100,
backgroundColor: 'white',
padding: 5,
},
You could not access the padding styles like styles.modalContainer.padding;
Currently:
However, the behaviour of this has changed. This is the source code of StyleSheet from the React Native team. Just copying the create method:
create<+S: ____Styles_Internal>(obj: S): $ObjMap<S, (Object) => any> {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
StyleSheetValidation.validateStyle(key, obj);
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
},
};
Which is just returning the object passed to create without doing anything to it. So you can actually access the styles as styles.modalContainer.padding;
Maybe I don't understand clearly the TODO, but this method has been coded this way at least since RN 0.57 and I don't know whether they are going to change it back.
My question:
Is there any sense in using StyleSheet.create anymore?
Thanks in advance for sharing your opinions!
Stylesheet is generally used to create a global style in react native and add it to the respective views which requires to style the objects.
Some widgets like TextInput, Text, Button cannot apply almost all the css styles in the react native.
So, in those cases what you can do is you can wrap those widgets with and then can create global StyleSheets using StyleSheet.create() method to globally use and reduce your headache.
So the conclusion for your question can be summarized as the Stylesheet.create() can be helpful to improve the performance while styling your multiple views using the same style will create a new object every time for each one.
While Stylesheet.create() will act as a single global object for all the views which are using it to style themselves resulting performance/memory optimisation.
I've never heard of this flatten() being necessary like you described. In fact, in the React Native repo in the very first commit, there was an example provided:
Examples/Movies/MovieCell.js:
https://github.com/facebook/react-native/commit/a15603d8f1ecdd673d80be318293cee53eb4475d#diff-4712aeb2165b3c0ce812bef903be3464
In this example, you can see var styles = StyleSheet.create({..}); being used in its present flavor and at that moment in 2016 you can see styles being referenced in the components as styles.styleName.
Additionally in the StyleSheet class here is create from the initial commit:
class StyleSheet {
static create(obj) {
var result = {};
for (var key in obj) {
StyleSheet.validateStyle(key, obj);
result[key] = StyleSheetRegistry.registerStyle(obj[key]);
}
return result;
}
// ...
}
As you see, no call to flatten on the initial commit, neither inside the create method, nor from the user using create.
In summary it seems this never changed and you could always access the styles using the dot operator.
As for whether to use it I don't think you have a choice. It clearly has some sort of validation code inside of it, it's also using type checking and the react team recommends using it. I don't see any other methods that do what it does. How could you use the class without create, just using some sort of init or constructor method? I don't see one on the class. There is no StyleSheet({...}); To get obj returned you need to call create.
Your editor could not give you IntelliSense if you strip away the validation behavior and make it a plain object. You won't know when you're making typos or referencing styles that don't exist, you won't have autocompletion. You'd need to create your own interfaces and use TypeScript. Thus you should use create because otherwise at a minimum you're breaking your IDE.

How to conditionally change a color of a row in detailslist?

I'm looking at customitemrows but there isn't much documentation.
I have a table and if a row was created by a current user, I want to change the color from black to grey. I can do that with style. I understand how to conditionally change color in customitemcolumns but can't extrapolate it to rows.
I got to:
_onRenderRow = (props) => {
return props.item['creatorUid'].match("QDN6k4pLXkea2qRM9mS7vM6whBE3")?
<DetailsRow {...props} style={{color:"#FF0000"}}/>
:
<DetailsRow {...props}/>
}
but the color doesn't change
<DetailsList
items={ items }
columns={ columns }
onRenderRow={ (props, defaultRender) => (
<div className='red'>
{defaultRender({...props, className: 'red'})}
</div>
) }
/>
<DetailsList
items={ items }
columns={ columns }
onRenderRow={ (props, defaultRender) => (
<div className='red'>
{defaultRender({...props, styles: {root: {background: 'red'}}})}
</div>
) }
/>
https://codepen.io/vitalius1/pen/pQmpVO
Here you can see 2 methods achieve what you ask for.
First is passing a regular className and have it override the default styles. If you need to override the hover states or anything else you would have to inspect in dev tools and target the appropriate classes.
Second is what is actually recommended and used internally to apply the default styles. With this method when you want to override the hover states or anything else, you would need to provide styles to each style area (in the example root is one of them). For a list of style areas available to each row follow this link. For an example on usage of the selectors for hover states follow this link.
Note: With the second method you can also pass a style function to make use of the IDetailsRowStyleProps as seen here. This way you can have a very dynamic style depending on the state of the component

Validate Style property type in React Native

I have an component that takes a property which contains the syling for a sub-component. I would like to ensure propTypes correctly validates it's type. I can from the React Native code that it has a ViewStylePropTypes module that provides this, however I cannot seem to find where/if it is exposed.
What I want to know is, what is the correct way of validating this without reinventing the wheel?
To enforce styling restrictions for PropTypes just use the following, dependent on what type of component you are rendering:
MyComponent.propTypes = {
/**
* Style to be applied to the containing <View>
*/
buttonStyle: View.propTypes.style,
/**
* Style to be applied to the inner <Text>
*/
textStyle: Text.propTypes.style
}
For example Text.propTypes.style will show a YellowBox warning when border is defined in thetextStyle property.
Note: This will also result in the regular Failed prop type supplied to Text... warning that occurs when rendering Text inside a component with an invalid style attribute. The propTypes validation allows your custom component to also validate this at the same time, giving you better granularity.

Get CSS property values from a component's "style" prop

I'm writing a React Native component for a library and I want users to be able to style it using the style property, just like React.View and other built-in components.
However, since my component is actually made up of a few nested views, I need to do some calculations to figure out what styling to put on the inner ones. For example, I might need to adjust the sizing of an image based upon the thickness of a border around it, or adjust a highlight color based upon the given text color, or in some other way infer some piece of styling from another piece of styling.
To do this, I need to be able to extract the actual CSS properties (like borderWidth: 2 or backgroundColor: 'pink') out of whatever gets passed as the style prop. This is fine as long as it comes as a plain object, but it may also be the result of a call to React.StyleSheet.create. This seems to be an opaque object with all selectors simply mapped to numeric IDs.
How can I resolve these and get the actual CSS properties, in order to do anything more complicated with them than simply passing them straight on to a View?
The built-in StyleSheet.flatten function (or the identical flattenStyle function) can turn anything that can legitimately be passed to the style prop into an object mapping CSS property names to values. It works on plain objects, IDs returned by StyleSheet.create(), and arrays.
Example usage to check the width specified in the style prop from within a Component definition:
import { StyleSheet } from 'react-native'
// ... then, later, e.g. in a component's .render() method:
let width = StyleSheet.flatten(this.props.style).width;
You need to import StylesheetRegistry:
StyleSheetRegistry = require("../../node_modules/react-native/Libraries/StyleSheet/StyleSheetRegistry"),
Then pass in the style ID:
var style = StyleSheetRegistry.getStyleByID(this.props.style)
Please have a look on https://github.com/vitalets/react-native-extended-stylesheet#underscored-styles
Style created via extended stylesheet contains original values in underscored prop:
const styles = EStyleSheet.create({
text: {
fontSize: '1rem',
color: 'gray'
}
});
In runtime:
styles = {
text: 0,
_text: {
fontSize: 16,
color: 'gray'
}
}