Is there a "special" syntax for adding styles to children of a React Native component? - react-native

So, in regular CSS, if I want to apply a style to all children of elements with a given id/class/element name, I would do this:
(id/class/name of parent element) * {
}
This will automatically apply to every child of the specified parent element(s) - I don't need to add any classes/ids/names, etc. to the children.
Is there an equivalent syntax in React Native? Best I can do so far is
view {
// style here
}
subelementsOfView {
}
and then add the subelementsOfView style to all children of View.
Am I wrong? Is there another syntax I can use?

You would create a StyleSheet as shown:
export const styles = StyleSheet.create({
someStyleKey: {
backgroundColor: 'red'
}
})
Wherever your children components are you can do the following:
import { styles } from '../<location-of-stylesheet>'
// Using View as an example component
<View style={styles.someStyleKey}></View>
If your child component already has a style you can also pass in an array of styles such as:
<View style={[existingStyle, styles.someStyleKey]}></View>
Whatever comes later in the style array will overwrite previous style values.

Related

Fluent UI React - how to apply global component styles with Fluent ThemeProvider

I'm working with the theming code below. I'm able to apply a global Fluent theme with the ThemeProvider and createTheme utility, but when I add component specific themes, I'm not getting any typings, which makes theming very difficult.
So my question is: how do I apply global component-specific styles using Fluent ThemeProvider with strong typing.
If, for example, I wanted to add a box shadow to all Fluent PrimaryButtons, I wouldn't know what properties to access on the components key passed into createTheme.
If you've done any global component theming, please let me know what pattern you used and if I'm on the right track, thanks!
import { createTheme } from '#fluentui/react';
import { PartialTheme } from '#fluentui/react-theme-provider';
// Trying to add global component styles (not getting typings)
const customComponentStyles = {
PrimaryButton: {
styles: {
root: {
background: 'red'
}
}
}
};
export const fluentLightTheme: PartialTheme = createTheme({
components: customComponentStyles, // Want to apply component styles
palette: {
themePrimary: '#0078d4',
themeLighterAlt: '#eff6fc',
themeLighter: '#deecf9',
themeLight: '#c7e0f4',
themeTertiary: '#71afe5',
themeSecondary: '#2b88d8',
themeDarkAlt: '#106ebe',
themeDark: '#005a9e',
themeDarker: '#004578',
neutralLighterAlt: '#faf9f8',
neutralLighter: '#f3f2f1',
neutralLight: '#edebe9',
neutralQuaternaryAlt: '#e1dfdd',
neutralQuaternary: '#d0d0d0',
neutralTertiaryAlt: '#c8c6c4',
neutralTertiary: '#a19f9d',
neutralSecondary: '#605e5c',
neutralPrimaryAlt: '#3b3a39',
neutralPrimary: '#323130',
neutralDark: '#201f1e',
black: '#000000',
white: '#ffffff'
}
});
The problem here is, that components is just a Record<string, ComponentStyles>, where ComponentStyles then just is a quite unspecific object of the form { styles?: IStyleFunctionOrObject<any, any>}. They would have to add an entry for every possible I<Component>Styles interface to ComponentsStyles, which I guess would be too much work and errorprone (e.g. forgetting to add a new component here ...).
Since all the I<Component>Styles interfaces are exported by Fluent, I always define the styles for each component separately and than merge them in the components object:
const buttonStyles: IButtonStyles = {
root: {
backgroundColor: 'red'
}
};
export const fluentLightTheme: PartialTheme = createTheme({
components: { PrimaryButton: { styles: buttonStyles } },
});
Here is a codepen example I created by using one of Fluent UIs basic Button examples: https://codepen.io/alex3683/pen/WNjmdWo

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>
);
}
}

Retrieve state values from many of the same child component react native

I have a screen that contains many of the same CustomSlider component. I would like to retrieve the slider values from every slider.
What is best practice for doing this in react native?
Here's a minimum working example, with 3 sliders:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import MultiSlider from "#ptomasroos/react-native-multi-slider";
class CustomSlider extends Component {
constructor(props) {
super(props)
this.state = {
multiSliderValue: [1, 9]
}
}
multiSliderValuesChange = values => {
this.setState({multiSliderValue: values});
};
render(){
return (
<MultiSlider
values={this.state.multiSliderValue}
onValuesChange={this.multiSliderValuesChange}
min={0}
max={10}
step={1}
/>
)
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
};
}
get_slider_values = () => {
// what is best practice to access the values of every slider here?
// eg an object like this
const slider_values = [[1.4, 7.4], [4.3, 7.0], [1.9, 3.2]]
return slider_values
}
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', padding: 50}}>
<CustomSlider />
<CustomSlider />
<CustomSlider />
<Text>{`The slider values are: ` + JSON.stringify(this.get_slider_values())}</Text>
</View>
);
}
}
There is no need for a complex solution. The way that I would handle this is to manage the state in the parent component. The CustomSlider doesn't really need to know its state. As the parent component needs to know the state of the sliders it is better to handle it there.
So as the parent component is going to handle the state this means we need to make some changes to what you are doing.
Set initial values in the parent component for the state of each of the sliders. This is important, it makes it means that even if the user doesn't touch the sliders we know the values of them.
Pass a function to each of the sliders that calls back to the parent component.
As the parent component is controlling the state we can remove the state from the CustomSlider. This gives a few options we could leave it as a Component, change it to a PureComponent or go one step further an change it to a Functional Component If the slider doesn't really need to know its state then the last option should be best for performance.
Here is how I would refactor your App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
sliderValues: [[1, 9],[1, 9],[1, 9]] // we should control the state here
};
}
// this uses function currying to bind the function and pass a value to it
onChange = (index) => (values) => {
this.setState( prevState => {
let sliderValues = prevState.sliderValues;
sliderValues[index] = values;
return {
sliderValues
}
})
}
render() {
return (
<View style={{alignItems: 'center', justifyContent: 'center', padding: 50}}>
<CustomSlider intialValues={this.state.sliderValues[0]} onChange={this.onChange(0)}/>
<CustomSlider intialValues={this.state.sliderValues[1]} onChange={this.onChange(1)}/>
<CustomSlider intialValues={this.state.sliderValues[2]} onChange={this.onChange(2)}/>
<Text>{`The slider values are: ` + JSON.stringify(this.state.sliderValues)}</Text>
</View>
);
}
}
Notice how we don't actually need a function to get the values of the sliders as they are stored in state. That means we can access the sliders' values directly by using this.state.sliderValues.
Here is your CustomComponent refactored to work with the above code:
class CustomSlider extends Component { // this could easily be swapped for a PureComponent
render(){
return (
<MultiSlider
values={this.props.intialValues}
onValuesChange={this.props.onChange}
min={0}
max={10}
step={1}
/>
)
}
Notice how it doesn't need to manage state at all as the parent component is handling it. It also means that we can remove a lot of code that isn't actually necessary. This is why I think we can go one step further and make it a Functional Component
const CustomSlider = ({intialValues, onChange}) => {
return (
<MultiSlider
values={intialValues}
onValuesChange={onChange}
min={0}
max={10}
step={1}
/>
)
}
If however if the CustomSlider needs to know its state because it is doing something more than capturing the values of the slider then you can easily add state to it by using it as a Component or a PureComponent.
Snack
Here is a snack showing the above code working. I have shown all three possible components and have used them in the App.js. There isn't much difference in how they look, but your use case will determine which one that you use. https://snack.expo.io/#andypandy/multisliders
Best Practice
The best practice is to go for the simplest solution that you can find. Ideally that would be a Functional Component, then a PureComponent, and finally a Component. It is also important to think about where and how the state is going to be used. Some questions that I ask myself are:
Does a component really need to know its own state?
Where do I plan on using that state?
How long do I need these state values for?
Do I need to persist that state?
What tools are available to me based on what I am currently using?
Do I really need to add another dependency or more to make this work?
If you need the values from the sliders in multiple places in your app you can use some of the features that are provided by react-native or your navigation to pass these values around. Redux and MobX are big overheads in terms of complexity and should only really be used if you need a global state management system, for the majority of cases they can be avoided.
You can store the state dynamically by some key given to each child, and access each ones state by the key you give it.
One way is to pass a closure from parent component to CustomSliders as props and monitor the changes.
<CustomSlider idx={n}
theClosurePassedThrough= (n, values) => {
// update the parents states here accordingly
}
>
Then call this closure at appropriate time.
multiSliderValuesChange = values => {
this.setState({multiSliderValue: values});
this.props.theClosurePassedThrough(this.props.idx, values);
};
The best practice, though, is to use MobX or Redux.

react-native prop type for text style

i have component with a simple structure and a <Text> somewhere inside the tree for which i want to pass in a style. Which works perfectly but for the proptypes validation.
The basic setup is not much more than that
export default class Component extends PureComponent {
render() {
return (<View><Text style={this.props.style}>Some text</Text></view>);
}
}
Component.defaultProps = {
style: null,
};
Component.propTypes = {
style: ViewPropTypes.style,
};
The problem is that the ViewPropTypes.style does not contain i.e. color key. So providing a style with a color is invalid and produces a warning. I tried to import TextStylePropTypes as i found in https://github.com/facebook/react-native/blob/master/Libraries/Text/TextStylePropTypes.js but it is undefined.
Any advice on what to do?
For anybody trying to achieve this seems like View.propTypes.style is deprecated while Text.propTypes.style is not.
As the passed style prop is for the Text node, use Text.propTypes.style as shown below...
Component.propTypes = {
style: Text.propTypes.style
};

React Native ref not called on dynamic component

I have a component that dynamically renders different components based off of props. I am unable to set the ref on one of them. This question had a duplicate ref, which I do not have. In addition, the components are built inside the render function.
Here is my code:
render() {
let content = null
switch(this.props.type) {
case 'follow'
content = <View ref={comp => this._followBtn = comp} />
break
// ...etc
}
return (
<View ref={comp => (this._wrapper = comp)>
{ content }
</View>
)
}
I need access to _followBtn from a parent component rendering the _wrapper.
I have only accomplished accessing _wrapper.
Does my problem stem from the fact that these are dynamically generated?
The ref prop never seems to be called.