StyleSheet.create() inside React Native component - reactjs

I understand that I should normally use StyleSheet.create() outside of the component, but lets say that I want to set the backgroundColor based on theme received from useTheme() hook from react-navigation. Since I can't call this hook outside of the component, I have to invoke it inside the component then do something like this:
export default () => {
const { colors } = useTheme()
return <View style={[styles.container, {backgroundColor: colors.background}]}></View>
}
Now, what if I implement above like this:
export default () => {
const { colors } = useTheme()
const componentStyles = useMemo(
() =>
StyleSheet.create({
container: {
...styles.container,
background: colors.background
}
}),
[colors]
)
return <View style={componentStyles.container}></View>
}
Is this a good practice? Specially when I have lot of cases like this and it makes the code looks messy.

You can also use state for backgroundColor. See:
export default () => {
const { colors } = useTheme();
const [background, setBackground] = React.useState();
React.useEffect(() => {
setBackground(colors.background);
}, [colors])
return
<View style={[styles.container, {backgroundColor: background}]} />
}

It's worth adding that StyleSheet.create() is not mandatory for creating styles in react native. An interesting discussion on this topic can be found here: Is there a need to use StyleSheet.create? . Personally, I do not use StyleSheet.create() .
Using TypeScript in react native I just create an object with the correct types. Example:
type Styles = {
modalView: ViewStyle;
button: ViewStyle;
modalText: TextStyle;
};
const styles: Styles = {
modalView: {
margin: 20,
backgroundColor: 'white',
borderRadius: 20,
padding: 35,
alignItems: 'center',
shadowRadius: 4,
elevation: 5,
},
button: {
padding: 10,
elevation: 2,
},
modalText: {
marginBottom: 15,
textAlign: 'center',
},
};

Hooks can only be used inside the components. If you need to add more styling from your theme into your components, as you know style prop takes an array. You can do something like this:
style={
[styles.container, //that comes from your StyleSheet created outside of the component
{
backgroundColor: theme.colors.your_color_choice, //Your theme values that comes from your useTheme hook
borderRadius: theme.borderRadius.your_radius_value,
},
]}
So that way you keep your styles object outside of the component, and they are not newly-created on each re-render.

Related

Error relating to props and renderItem in react native flatlist

im making a simple hello world type project in react native to familiarize myself with everything before I move onto my first project.
The goal of this one was to display an array with a flatlist, but the kicker was I was going to slowly modularize it (ie. array is stored somewhere else, flatlist uses renderitem, renderitem is a seperate component, ect.)
import React, {useState} from 'react';
import {StyleSheet, Text, View, FlatList} from 'react-native';
function App () {
const [orders, setOrders] = useState([
{ customer: 'Clair' , age: 45, item: 'Corn', key: 0},
{ customer: 'Alex' , age: 39, item: 'Tomato', key: 1},
]);
const renderer = ({order}) =>
{
const customerName = order.customer;
const itemName = order.item;
return(
<Text>I am {customerName} and I would like a {itemName} please</Text>
);
};
return(
<View style={styles.container}>
<FlatList
data={orders}
renderItem={renderer}
keyExtractor={(order) => order.key}
/>
<Text> Hello World!!!! </Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
orderContainer:
{
borderColor: 'black',
borderWidth: 5,
padding: 20,
alignItems: 'center',
},
listText:
{
justifyContent: 'center',
alignItems: 'center',
}
});
export default App;
the first dummy mistake ive learned is that you shouldnt declare a "component" (in this case the renderer) inside the main return statement for the app
now I just need to wrap my head around the prop handling. The error specifically has to do with the renderItem line. apparently the prop, which from my understanding should be one of the orders from the useState array, doesnt exist
any help is appreciated :)
The object renderer get is as follows.
item: Object
index : 0
separators: Object
Thus to get the customer details you need to update your function to follow.
const renderer = ({item}) =>
{
const customerName = item.customer;
const itemName = item.item;
return(
<Text>I am {customerName} and I would like a {itemName} please</Text>
);
};

How can a Styled Components Theme be passed as Props to a Material UI component?

I have spent 9 hours searching for a resolution to this any nothing I can find anywhere appears to work.
I am writing a React component in typescript.
I have a simple use of the Material UI Accordian:
const Accordion = withStyles({
root:{
backgroundColor: 'rgb(193, 195, 191)',
},
rounded:{
'&:last-child':{
borderBottomLeftRadius:'0px',
borderBottomRightRadius:'0px',
},
'&:first-child':{
borderTopLeftRadius:'0px',
borderTopRightRadius:'0px',
}
}
})(MuiAccordian);
All I am trying to do is pass in my Styled Components theme so I can apply it as the root=>backgroundColor value.
I am sure some wizard in here will spot my issue immediately - can I please ask someone to simply show how to pass in a theme of type DefaultTheme object as props to the withStyles method?
Many thanks for any help.
EDIT - CODE SOLUTION
With thanks to #filippofilip below, the links in his answer point to information that allowed me to solve this.
The working code is:
const Accordion = withStyles(
{
root: (props:DefaultTheme) => ({
backgroundColor: props.colors.backgroundColor,
}),
rounded: {
'&:last-child': {
borderBottomLeftRadius: '0px',
borderBottomRightRadius: '0px',
},
'&:first-child': {
borderTopLeftRadius: '0px',
borderTopRightRadius: '0px',
},
},
},
{ withTheme: true },
)(MuiAccordian);
You will note the slight difference in parsing the typed props which is of type DefaultTheme from Styled Components.
To call this from implementation, I used the useContext hook to retrieve the Styled Components Theme and pass that to the props:
export default function MyAccordianComponent() {
const themeContext = useContext(ThemeContext);
const classes = useStyles(themeContext);
return (
<div className={classes.root}>
<Accordion {...themeContext} defaultExpanded >
---fill in the rest of the component below
I hope this assists someone else looking for this or similar to work with Styled Components, Typescript and Material UI
I think you should try to checkout this documentation example
https://material-ui.com/styles/basics/#adapting-based-on-props
Usually theme is inside of props argument or as a second argument coming from that function.
EDIT: I found it in documentation you should explicitly specify that you want have theme inside props object.
So just use it like this:
const Accordion = withStyles(
{
root: {
backgroundColor: props => props.theme.backgroundColor,
},
rounded: {
'&:last-child': {
borderBottomLeftRadius: '0px',
borderBottomRightRadius: '0px',
},
'&:first-child': {
borderTopLeftRadius: '0px',
borderTopRightRadius: '0px',
},
},
},
{ withTheme: true },
)(MuiAccordian);
To checkout all other available options look HERE

React native require with backticks

I'm implementing an app and on the homescreen I have a flatlist element with inside for each item a card element.
import React, { useEffect, useContext } from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import {ListItem, Card} from 'react-native-elements';
import { Context as ParkingContext } from '../context/ParkingContext';
const HomeScreen = () => {
const { state: {records}, fetchParkings } = useContext(ParkingContext);
useEffect(() => {
fetchParkings();
},[])
return (
<View style={styles.container}>
<FlatList
data={records}
keyExtractor={item => item.recordid}
renderItem={({item}) => {
return (
<Card containerStyle={styles.cardStyle} title={item.fields.description}>
<Text style={styles.textStyle}>{item.fields.address.replace(/\n/g,'')}</Text>
<Text style={styles.textStyle}>Aantal vrije plaatsen: {item.fields.availablecapacity}</Text>
</Card>
)
}}
/>
</View>
)
};
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 25
},
cardStyle: {
padding: 0,
borderRadius: 10
},
textStyle: {
flexDirection: 'row',
flex: 1,
marginBottom: 10,
marginLeft: 5
}
});
export default HomeScreen;
The card element has a property image which can be used to set a image to the card. I want to give different images to the cards by doing
image={require(`../../assets/${item.fields.description}.jpg`)}
but require doesn't let me do this with backticks and neither with a variable... is there some way to use backticks or a variable so I can have different photo's for the different cards?
My apologies if the answer is obvious but I tried a lot of things and it does not work.
Assuming you created your react app with create-react-app or you are using webpack to bundle your code then what you are trying to achieve will not work: Since Webpack is running in build-time, it can't figure out which modules to bundle when the name is a dynamic variable. Packaging happens once before runtime so those variables don't have values yet.
If you have a small number of images, you can do something like this:
let image1 = require('../../assets/xxx.jpg');
let image2 = require('../../assets/xxx.jpg');
...
image={image1}
...
But of course, if you have a long list of images this way will not be optimal
Further details can be found in the discussion of this issue

How do you change the color of a stepper in material-ui in React

Referring to this:
https://material-ui.com/demos/steppers/
It is blue by default.
I want it to be material-ui's orange200
I tried the following code (from this stackoverflow answer) but it did not work.
import getMuiTheme from 'material-ui/styles/getMuiTheme'
const muiTheme = getMuiTheme({
stepper: {
iconColor: 'green' // or logic to change color
}
})
<MuiThemeProvider muiTheme={muiTheme}>
<Stepper>
...
</Stepper>
</MuiThemeProvider>
Use Chrome DevTools (or other browsers' dev tools) to find out a class that will give you an information about an element to override.
For example, assuming you found that the class name is
.MuiStepIcon-root-78. The formulae is Mui[component name]-[style rule name]-[UUID], where Mui is a default prefix and 78 is just an id. So, the element's name is MuiStepIcon and a subsequent attribute is root.
Say, you want to change the color. If you do the hierarchy MuiStepIcon -> root -> color, you will change the default color only. To change any other colors, watch out for pseudo classes. For example, using devtools, if the class is .MuiStepIcon-root-78.MuiStepIcon-active-80, then the pseudo class is active, and your code should be MuiStepIcon -> root -> '&$active' -> color. Look at the code below for references.
Look at the docs for more info https://material-ui.com/customization/overrides/#overriding-with-classes
You can also determine available elements to override by referring to createMuiTheme -> overrides, which will take you to overrides.d.ts file. There is an interface that lists all components names, like MuiStepIcon, though it won't give you other information as devtools do.
import React, { Component } from 'react';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
const muiTheme = createMuiTheme({
overrides: {
MuiStepIcon: {
root: {
color: '#000000', // or 'rgba(0, 0, 0, 1)'
'&$active': {
color: '#000000',
},
'&$completed': {
color: '#000000',
},
},
},
}
});
const otherStyles = theme => ({
root: {
// Whatever needed
},
});
class MyComponent extends Component {
render(){
return (
<MuiThemeProvider theme={muiTheme}>
{
// Your stepper here, should be within MuiThemeProvider
}
</MuiThemeProvider>
);
}
};
export default withStyles(otherStyles, { withTheme: true })(MyComponent);
overrides: {
MuiStepIcon: {
root: {
color: '#000000', // or 'rgba(0, 0, 0, 1)'
'&$active': {
color: '#000000',
},
'&$completed': {
color: '#000000',
},
},
}
did work for me
One way to change the color and styling of icon of stepper material UI is to pass icon prop in StepLabel as:
<StepLabel
icon = <div style={{backgroundColor: 'orange', width:'11px', padding: '2px', textAlign: 'center', height: '11px', fontSize: '10px', borderRadius: '50%'}}>{index}</div>
>{label}</StepLabel>

Merge / Combine two or more different StyleSheet components in React Native?

I'm separating my styles in the following way:
styles /
|-- base.js
|-- base.ios.js
|-- base.android.js
Each of them exports a StyleSheet component created as in this example:
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
statusBar: {
height: 20
});
How can I merge them so I only have one base style object? I'm looking for something like:
const baseStyles = mergeStyles(baseStyle, platformStyle);
you are very close:
const baseStyles = [baseStyle, platformStyle];
basically any component can cascade styles like this:
<View style={[styles.style1,styles.style2]}></View>
You can also use StyleSheet.flatten method. See documentation here.
var styles = StyleSheet.create({
listItem: {
flex: 1,
fontSize: 16,
color: 'white',
},
selectedListItem: {
color: 'green',
},
});
StyleSheet.flatten([styles.listItem, styles.selectedListItem]);
// returns { flex: 1, fontSize: 16, color: 'green' }
UPDATE:
StyleSheet.flatten internally uses StyleSheetRegistry.getStyleByID(style) to resolve style objects represented by IDs. IDs enable optimizations through the bridge and memory in general. Referring to style objects directly will deprive you of these optimizations.
So flatten method is better than style={ [ styles.listItem, styles.selectedListItem ] }
You can combine style sheets using the spread operator '...', be warned that any variables of the same name will be overwritten by the last instance.
Heres a small demo app to demonstrate:
'use strict';
import React, { Component } from 'react';
import {
Alert,
Button,
StyleSheet,
Text,
AppRegistry,
View,
} from 'react-native';
class listTest extends Component {
render() {
return (
<View style={styles3.myViewBox}>
<Text style = {styles3.myTextBox1}>
TEST
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
myTextBox1: {
backgroundColor:'red',
},
myViewBox: {
backgroundColor:'blue',
margin:15,
padding:15,
}
});
const styles2 = StyleSheet.create({
myTextBox2: {
backgroundColor:'yellow',
},
myViewBox: {
backgroundColor:'green',
margin:15,
padding:15,
},
});
const styles3 = {...styles,...styles2};
AppRegistry.registerComponent('listTest', () => listTest);
EDIT:
If you're running ES5 you can just use:
const styles3 = Object.assign(styles,styles2);
Now ReactNative lets you combine two styles using a more functional approach. Use the function StyleSheet.compose so you can do the following as per documentation:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const App = () => (
<View style={container}>
<Text style={text}>React Native</Text>
</View>
);
const page = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: '#fff',
},
text: {
fontSize: 30,
color: '#000'
},
});
const lists = StyleSheet.create({
listContainer: {
flex: 1,
backgroundColor: '#61dafb',
},
listItem: {
fontStyle: 'italic',
fontWeight: 'bold'
},
});
const container = StyleSheet.compose(page.container, lists.listContainer); // if you wanna compose more than one style just use a map.reduce function and compose two at the time until you have consumed you list of styles
const text = StyleSheet.compose(page.text, lists.listItem);
export default App;

Resources