How to pass props to StyleSheet on custom view in React Native? - reactjs

So, I have a custom view. On that view, besides taking the children components, I also wanna take backgroundColor and some other StyleSheet property so I can style it depends on the screens.
This is the App.tsx.
export const MainScreen = ({}: Props) => {
return (
<CustomView backgroundColor={"#000"}>
<Text>Example</Text>
</CustomView>
);
};
And this is the CustomView.tsx
type Props = {
children: React.ReactNode;
backgroundColor: string;
};
export const CustomView = ({ children, backgroundColor }: Props) => {
return <View style={styles.container}>{children}</View>;
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
Say, I wanna change the background color for this screen to #000 like on the codes above. But, I don't know how to handle the props on the CustomView so it can change the backgroundColor.
I tried writing this code
return <View style={styles.container, {backgroundColor: backgroundColor}}>{children}</View>;
But it overwrites the styles.container. On the other hand, if I write this
return <View style={{backgroundColor: backgroundColor}, styles.container}>{children}</View>;
It always throws the error that the left side of comma (the backgroundColor itself) is unused and has no side effects.
So, what should I do? How can I pass the props to handle the StyleSheet?

You can pass an array to the style prop.
According to the docs:
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.
return <View style={[styles.container, { backgroundColor: backgroundColor }]}>{children}</View>;

Related

How to declare types on Typescript for spread props

I am trying to figure out how to get this code to work on TypeScript
type ScrollProps = {
autoHide: boolean
autoHideTimeout: number
autoHideDuration: number
}
const renderThumb = ({ style, ...props}) => {
const thumbStyle = {
borderRadius: 6,
backgroundColor: 'rgba(35, 49, 86, 0.8)'
}
return <div style={{ ...style, ...thumbStyle }} {...props} />
}
const CustomScrollbars = (props: ScrollProps) => (
<Scrollbars
renderThumbHorizontal={renderThumb}
renderThumbVertical={renderThumb}
{...props}
/>
)
...
<CustomScrollbars autoHide autoHideTimeout={500} autoHideDuration={200}>
...
</CustomScrollbars>
If I create a type for the props on CustomScrollbars I get an error on <CustomScrollbars>, if I don't I get an error on the props at the CustomScrollbars function.
I also get an error on style, it wants to know what type, but I do not know what type to put.
Why does the implicit any not work and how can I make this code to work on TypeScript?
https://codesandbox.io/s/react-hooks-with-typescript-forked-8g5g6
In all honestly the types for the react-custom-scrollbars package are really lacking. The expected type for the renderThumbHorizontal and renderThumbVertical props is declared as React.StatelessComponent<any> -- a component that takes any props. So I have no idea if the props that those components will be called with match the props that your renderThumb is expecting.
As far as declaring the props for renderThumb (which I recommend capitalizing, as is will create problems if you try to use it in JSX like <renderThumb />), the spread operator doesn't really matter. You are accepting an object of props and all of them are passed down to a div. So we want to accept whatever props a div would accept. That type is JSX.IntrinsicElements['div'].
With this definition, style is type React.CSSProperties | undefined. It's actually ok to spread undefined, so you don't need to require style or set a default.
const RenderThumb = ({ style, ...props}: JSX.IntrinsicElements['div']) => {
const thumbStyle = {
borderRadius: 6,
backgroundColor: 'rgba(35, 49, 86, 0.8)'
}
return <div style={{ ...style, ...thumbStyle }} {...props} />
}
As far as the CustomScrollbars, you can import the props from the package rather than having to redefine them yourself. If you type the component as React.FunctionComponent<Props> that will include the children prop automatically. (But something is wonky with your CodeSandbox because it can't find that type).
import React from "react";
import { Scrollbars, ScrollbarProps } from "react-custom-scrollbars";
const CustomScrollbars: React.FC<ScrollbarProps> = (props) => (
<Scrollbars
renderThumbHorizontal={renderThumb}
renderThumbVertical={renderThumb}
{...props}
/>
);
I think you need to add children into the props type.
And for style, add the following type
const renderThumb = ({ style, ...props}: {style: React.CSSProperties}) => {
}

pass material ui styles to component - react

I am using a functional component with React, I need to show SVG Icon based on state and I want to load the relevant icon
so the parent will show only call :
<icon classes:... , state..></icon>
1- how can I pass style and if it does not exist and use a default style in the child?
now I have smth like in the parent :
... createStyle
IconSuccess: {
fontSize: 20,
width: 20,
},
IconWarning: {
fontSize: 20,
width: 20,
},
but i want smth like :
icon:{
width:..
font ..
warning: { color}
success: { color}
}
then
<IconChild state={state} classes={{ icon: itemStyle.icon}} />
this is work only if I pass specific style like:
<IconChild state={state} classes={{ iconWarning: itemStyle.iconWarning}} />
then in the childCOmponent I am doing smth like:
const classes = useStyles(props);
if( props.state == 1){
return <className={`${classes.iconWarning}`} />
}
else{
return <className={`${classes.iconSuccess}`} />
}
so basically I am trying to understand how to create a really generic component that I can use and pass and that need a state to choose the specific icon and also from specific class
do I need HOC ? or different approach
As I understand, you want to:
Reuse some common properties like width and fontSize.
Custom render other properties like color.
Then this is my approach:
First, make new style for commonly used properties.
Secondly, create new styles for conditional use of each state.
Last, use something like classnames to combine all classes.
So the main idea here is: instead of using one class for each item, now using two classes for each one. That's it!
const useStyles = withStyles({
commonProperty: {
fontSize: '20px',
width: '20px',
},
successOnlyProperty: {
color: 'green'
},
warningOnlyProperty: {
color: 'orange'
},
});

Remove (or at least hide) card on react-admin List

I want to get rid of the Card on the background react-admin's List (v3.4.2). I get the desired effect if I define a string on the component property:
<List component={"some string"}/>
But this spams the console with an error:
And I don't want to have that error. On top of that, I think I shouldn't be changing the component property (I can't find it on the official docs).
The code should be the following: https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/list/List.js
How should I do this? Is it possible to pass a style? Is there any component that works out of the box? Or should I just go custom?
You can hide the background using styling:
import { makeStyles } from '#material-ui/core/styles'
const useListStyles = makeStyles(theme => ({
content: {
boxShadow: 'none',
backgroundColor: 'inherit',
},
main: {
// backgroundColor: 'red',
},
root: {
// backgroundColor: 'red',
},
}))
const MyList = (props) => {
const classes = useListStyles()
return (
<List classes={classes} {...props} >
...
</List>
)
}

React Native Dynamic Styling

I am creating s crypto app where I want to change background color to red when the price went down and green when Price went up and then after 2 seconds change the background back to normal using setTimeout.
I tried two different methods to at-least change the backgroundColor but on both the occasion i got the following error
You attempted to set the key backgroundColor with the value #ffe5e5
on an object that is meant to be immutable and has been frozen.
I asked the separate question for the same but for some reason, the response I received was not convincing.
Afterwords, i tried a different approach (the one which does not allow the use of StyleSheet) but I still got the same error.
I am putting my new code here (you can refer to my previous code from the question)
First I declared an object in a global scope like this
var upperRow = {
display: "flex",
flexDirection: "row",
marginBottom: 5,
backgroundColor: "white"
}
class CoinCard extends Component {
then I tried to change background color like this
componentWillReceiveProps(nextProps) {
if (this.props.coinPrice != nextProps.coinPrice ) {
if (this.props.coinPrice > nextProps.coinPrice) {
upperRow["backgroundColor"] = "#ffe5e5";
}
}
followed by assigning the styles like this
return (
<View style={container}>
<View style={upperRow}>
[Question:] How can I change the styling dynamically?
You'll need to keep in state whether or not your coin has decreased recently.
Imagine a state of this shape:
state = {
hasIncreased: boolean,
hasDecreased: boolean,
}
Now you can change this state in the componentWillReceiveProps (this is now deprecated though, so if you are on 16.3 or higher, maybe look in phasing its use out) like so:
componentWillReceiveProps(nextProps) {
if (nextProps.coinPrice < this.props.coinPrice)
this.setState({hasDecreased: true, hasIncreased: false})
if (nextProps.coinPrice > this.props.coinPrice)
this.setState({hasIncreased: true, hasDecreased: false})
}
Now that you have this state, you can add some styles conditionally:
render() {
const {hasIncreased, hasDecreased} = this.state
return (
<View style={[ //styles can be an array!
upperRow,
hasIncreased && {backgroundColor: 'green'},
hasDecreased && {backgroundColor: 'red'},
]}/>
)
}
Now all that's left is to reset state after 2 seconds. To me, it seems best to use the componentDidUpdate-lifecycle for that
componentDidUpdate(prevProps) {
// only queue the reset if it's actually necessary, not on random updates
if (prevProps.coinPrice !== this.props.coinPrice) {
clearTimeout(this.timeout)
// we need to be able to reset the timeout
this.timeout = setTimeout(
() => this.setState({hasDecreased: false, hasIncreased: false}),
2000,
)
}
}
You should try avoiding just changing the styles like that in a mutable manner. The whole point is that your state should reflect what you display.
Pass modalHeight dynamically from parent component as props
modalView is default style
<View style={[styles.modalView, { height: `${modalHeight}` }]}>

material-ui: AppBar: strategy for restricting an image height to AppBar height?

can anyone provide guidance around an idiomatic way to place an image in an AppBar and have it be restricted to the standard material height (e.g. 64px for a desktop)?
i'm currently using material-ui#next (1.0.0-beta.2 currently).
i have found that something like:
<AppBar>
<Toolbar>
<IconButton color="contrast" aria-label="Menu">
<MenuIcon />
</IconButton>
<img src={logo} style={{height: 64}}/>
<Typography type="title" color="inherit">
React Scratch
</Typography>
</Toolbar>
</AppBar>
works well.
the actual logo is a png file with a height greater than 64, so if i don't ratchet it down, it expands the height of the AppBar out of Material spec.
in the current master branch version of src/styles there is a getMuiTheme.js which seems to deliver this height readily, but in the #next version i am looking at, that file doesn't even exist and tbh, i can't easily determine how that height is being set anymore.
i found that the AppBar is currently being renovated for composability, so that churn might make it challenging to answer this question, but just in case anyone has a good handle on this, i figured i would toss the question out there.
thanks!
In all cases I've seen, an AppBar is implemented with a Toolbar as it's first child. The Toolbar's stylesheet dictates it's height based on the breakpoints defined in the theme.
Take a look here: https://github.com/callemall/material-ui/blob/v1-beta/src/Toolbar/Toolbar.js
You can use a similar approach to define a stylesheet with a class for your AppBar images that varies the height for the applicable breakpoints. Then when rendering the component, apply the class to your image.
Note: if you use the withStyles HOC, as is done in the Toolbar, AppBar etc, the classes defined in that stylesheet will be available through a prop named classes.
You are right about the AppBar's need for composability, but that issue has not been solved yet, and this is the beta branch anyway. When it is solved, there should be a better solution that would be worth migrating towards.
I hope this answer helps. I would have added code samples but I am answering from my phone while waiting in a grocery store parking lot. If I get a chance I will update this answer.
Here's one approach, duplicating the styles in a new reusable component:
import createStyleSheet from 'material-ui/styles/createStyleSheet';
import withStyles from 'material-ui/styles/withStyles';
// define these styles once, if changes are needed because of a change
// to the material-ui beta branch, the impact is minimal
const styleSheet = createStyleSheet('ToolbarImage', theme => ({
root: {
height: 56,
[`${theme.breakpoints.up('xs')} and (orientation: landscape)`]: {
height: 48,
},
[theme.breakpoints.up('sm')]: {
height: 64,
},
},
}));
// a reusable component for any image you'd need in a toolbar/appbar
const ToolbarImage = (props) => {
const { src, classes } = this.props;
return (
<img src={src} className={classes.root} />
);
};
// this higher order component uses styleSheet to add
// a classes prop that contains the name of the classes
export default withStyles(styleSheet)(ToolbarImage);
Another approach is to add the standard toolbar heights to the theme as business variables, override the root class for all Toolbars so that it makes use of them, and use the theme whenever you need to reference them again:
// define the standard heights in one place
const toolbarHeights = {
mobilePortrait: 56,
mobileLandscape: 48,
tabletDesktop: 64,
};
// create the theme as you normally would, but add the heights
let theme = createMuiTheme({
palette: createPalette({
primary: blue,
accent: pink,
}),
standards: {
toolbar: {
heights: toolbarHeights,
},
},
});
// recreate the theme, overriding the toolbar's root class
theme = createMuiTheme({
...theme,
overrides: {
MuiToolbar: {
// Name of the styleSheet
root: {
position: 'relative',
display: 'flex',
alignItems: 'center',
minHeight: theme.standards.toolbar.heights.mobilePortrait,
[`${theme.breakpoints.up('xs')} and (orientation: landscape)`]: {
minHeight: theme.standards.toolbar.heights.mobileLandscape,
},
[theme.breakpoints.up('sm')]: {
minHeight: theme.standards.toolbar.heights.tabletDesktop,
},
},
},
},
});
Then you can reference these heights in any stylesheet you create because they're part of the theme.
UPDATED FOLLOWING THE RELEASE OF 1.0.0-beta.11:
There is now a toolbar mixin available on the theme that provides the toolbar minHeight for each breakpoint. If you need to style an element relative to the standard height of the AppBar component, you can use this object to build your own styles:
const toolbarRelativeProperties = (property, modifier = value => value) => theme =>
Object.keys(theme.mixins.toolbar).reduce((style, key) => {
const value = theme.mixins.toolbar[key];
if (key === 'minHeight') {
return { ...style, [property]: modifier(value) };
}
if (value.minHeight !== undefined) {
return { ...style, [key]: { [property]: modifier(value.minHeight) } };
}
return style;
}, {});
In this example, toolbarRelativeProperties returns a function that will return an object that can be spread into your style object. It addresses the simple case of setting a specified property to a value that is based on the AppBar height.
A simple usage example would be the generation of a dynamic CSS expression for height calculation, which is depending on the standard height of the AppBar:
const componentStyle = theme => ({
root: {
height: toolbarRelativeProperties('height', value => `calc(100% - ${value}px)`)(theme)
}
});
The generated style definition might look like this:
{
height: 'calc(100% - 56px)',
'#media (min-width:0px) and (orientation: landscape)': {
height: 'calc(100% - 48px)'
},
'#media (min-width:600px)': {
height: 'calc(100% - 64px)'
}
}

Resources