Coming from react-native, i am trying to build a component to display data with. What i am having trouble with, is combining the styles defined within the component itself, with the ones being passed as props from outside.
In react-native this is simply achieved by putting the 2 styleobjects inside an array, but how do i do this in react?
export interface MenuItemProps {
'containerStyle'?: React.CSSProperties,
}
export const MenuItem: React.FC<MenuItemProps> = (props) => {
const { title, selected, onClick, containerStyle } = props;
const mystyle = {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
marginBottom: 10,
}
return (
<React.Fragment>
<div
style={[{mystyle, containerStyle}]}
onClick={() => onClick()}
You can combine the styles via rest operator to combine two objects with styles in one in prop style.
style={{...mystyle, ...containerStyle}}
what i ended up doing was add the incoming containerStyle to the end of myStyle, so that incoming styling overwrites existing styling;
const myStyle: React.CSSProperties = {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginTop: 10,
marginBottom: 10,
...containerStyle,
}
Related
The code to render a TabList:
import React, { Children, useEffect } from 'react';
import { LayoutChangeEvent, View } from 'react-native';
import {
ScrollView,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { isValidChild } from '#utils';
import { useTabIndex } from '../tab-context';
import { useStyle } from './tab-list.styles';
import { TabListProps } from './tab-list.type';
const animConfig = {
duration: 200,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
};
const TabList: React.FC<TabListProps> = props => {
const styles = useStyle();
const { children, onChange } = props;
const selectedTabIndex = useTabIndex();
const animatedTabIndicatorPosition = useSharedValue(0);
// Save layout of the container
const [containerLayout, setContainerLayout] = React.useState({
x: 0,
y: 0,
width: 0,
height: 0,
});
const onContainerLayout = (event: LayoutChangeEvent) => {
const { x, y, width, height } = event.nativeEvent.layout;
setContainerLayout({ x, y, width, height });
};
// get children length
const childrenLength = Children.count(children);
const tabWidth =
childrenLength > 3
? containerLayout.width / 3
: containerLayout.width / childrenLength;
const renderChildren = () => {
// Render only children of component type TabList
return Children.map(children, child => {
// Check if child is a valid React element and has type TabList
if (isValidChild(child, 'Tab')) {
return (
<TouchableWithoutFeedback
containerStyle={{ width: tabWidth }}
onPress={() => onChange((child as JSX.Element)?.props.tabIndex)}
>
{child}
</TouchableWithoutFeedback>
);
}
// Throw error if child is not a TabList
throw new Error('TabList component can only have children of type Tab');
});
};
useEffect(() => {
animatedTabIndicatorPosition.value = selectedTabIndex * tabWidth;
}, [selectedTabIndex]);
const indicatorAnimatedStyle = useAnimatedStyle(() => {
return {
width: tabWidth,
transform: [
{
translateX: withTiming(
animatedTabIndicatorPosition.value,
animConfig,
),
},
],
};
}, []);
return (
<View onLayout={onContainerLayout} style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
testID="TestID__component-TabList"
>
<Animated.View
style={[styles.indicatorContainer, indicatorAnimatedStyle]}
>
<View
style={[
styles.indicator,
{
width: tabWidth - 4,
},
]}
/>
</Animated.View>
{renderChildren()}
</ScrollView>
</View>
);
};
export default TabList;
The styles for the component elements:
import { createUseStyle } from '#theme';
// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
container: {
position: 'relative',
flexGrow: 1,
backgroundColor: theme.palette.accents.color8,
height: 32,
borderRadius: theme.shape.borderRadius(4.5),
},
indicatorContainer: {
position: 'absolute',
height: 32,
justifyContent: 'center',
alignItems: 'center',
},
indicator: {
height: 28,
backgroundColor: theme.palette.background.main,
borderRadius: theme.shape.borderRadius(4),
},
}));
I am using react-native-reanimated to animate the tab indicator. What I noticed is, on app reload, the initial tab indicator position keeps on changing as seen in the GIF I have attached. At times, it is positioned where it should be and at times, half the box is hidden behind the scrollview container. When I remove the alignItems: center from the Animated.View, things work as expected.
I am perplexed as to why the position keeps changing because of align-items?
The issue was that the child indicator component wasn't wrapped within the boundary of the indicator container. I resolved this by adding flexWrap: 'wrap' to the parent indicator container.
So, the new style looks like this:
import { createUseStyle } from '#theme';
// createUseStyle basically returns (fn) => useStyle(fn)
export const useStyle = createUseStyle(theme => ({
container: {
position: 'relative',
flexGrow: 1,
backgroundColor: theme.palette.accents.color8,
height: 32,
borderRadius: theme.shape.borderRadius(4.5),
},
indicatorContainer: {
position: 'absolute',
height: 32,
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap'
},
indicator: {
height: 28,
backgroundColor: theme.palette.background.main,
borderRadius: theme.shape.borderRadius(4),
},
}));
Edit: disregard the below, the behavior is not due to hot reloading! I'll leave this up in case anyone else has the same misconception
Hot reloading is not reliable with Reanimated - there are values on native threads that won't get refreshed. This has no impact on the final app.
To test whether it's really working, simply shake the device/sim and hit Reload after you make changes. This is enough to clear any sticky values. If your component still isn't doing what you want, you can then have the confidence to edit it and be sure it looks right.
I am currently working on a React Native app with the following layout:
(full source code can be found at https://snack.expo.io/#ajaybhatta49/new-project, but I think the issue is in this file)
import React, {useState} from 'react'
import { View, StyleSheet } from 'react-native'
import CButton from './components/CButton'
import Display from './components/Display'
const buttonRows = [
["1", "2", "3", "+"],
["4", "5", "6", "-"],
["7", "8", "9", "x"],
[".", "0", "<", "/"],
["CLEAR", "ENTER"]
]
export default function App(props) {
const [input, setInput] = useState('')
const handlePress = str => {
if (str === "CLEAR") {
setInput('')
} else if (str === "ENTER") {
try {
setInput(eval(input))
} catch(err) {
setInput("SYNTAX ERROR")
}
} else if (str === "x") {
setInput(`${input}*`)
} else if (str === "<") {
setInput(input.slice(0, input.length - 1))
} else {
setInput(input + str)
}
}
return (
<View style={styles.main}>
<Display value={input}/>
{buttonRows.map(buttons => {
return (
<View style={styles.inline}>
{buttons.map(b => {
return (
<CButton title={b} handlePress={() => handlePress(b)}/>
)
})}
</View>
)
})}
</View>
)
}
const styles = StyleSheet.create({
main: {
flex: 1,
flexDirection: 'column',
alignContent: 'center',
justifyContent: 'space-evenly',
alignItems: 'center',
backgroundColor: '#222',
padding: 5
},
inline: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
height: 5
}
})
The app works fine, but I want to get rid of the extra space between the rows of buttons, and preferably align everything to the center vertically. I have tried changing the value of styles.main.justifyContent to space-evenly, space-around, flex-start, and center, none of which resolved the issue. I also tried a similar question's answer, which was to get rid of one of the flex: 1's, but the app crashes unless both of them are there. What should I do?
Whether justifyContent works vertically or horizontally depends on flex direction.So in styles.inline when you use flex while your direction is column, it makes the column take up all the vertical space it could. I removed height because it appearred to break the layout (idk why)
const styles = StyleSheet.create({
main: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#222',
padding: 5
},
inline: {
flexDirection: 'row',
}
})
Trying to add styles to a Material-UI chip (outlined variant) upon hovering, but not getting the expected results.
The border color is white, but the background color doesn't change at all.
So I'm questioning whether backgroundColor is even the right property anymore, but what else can it be?
const CustomChip = withStyles(theme => ({
root: {
"&:hover": {
borderColor: "white",
backgroundColor: "green"
}
}
}))(Chip);
Below are the default background-color styles for the outlined variant of Chip:
/* Styles applied to the root element if `variant="outlined"`. */
outlined: {
backgroundColor: 'transparent',
'$clickable&:hover, $clickable&:focus, $deletable&:focus': {
backgroundColor: fade(theme.palette.text.primary, theme.palette.action.hoverOpacity),
},
In the styles above, $clickable& will be resolved to .MuiChip-clickable.MuiChip-outlined. The important aspect being that this rule is specified using two class names in addition to the pseudo-class (:hover or :focus). This means that these default styles will have greater specificity than the style rule you used for your override (which only uses one class name plus the pseudo-class). In order for your override to be successful, it needs to have specificity equal to or greater than the default styles.
One simple way to do this is to double the &. This causes the generated class name (which the ampersand refers to) to be specified twice in the rule -- increasing its specificity to match the default styles.
Here's a working example:
import React from "react";
import { makeStyles, withStyles } from "#material-ui/core/styles";
import Avatar from "#material-ui/core/Avatar";
import Chip from "#material-ui/core/Chip";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "center",
flexWrap: "wrap",
"& > *": {
margin: theme.spacing(0.5)
}
}
}));
const StyledChip = withStyles({
root: {
"&&:hover": {
backgroundColor: "purple"
},
"&&:focus": {
backgroundColor: "green"
}
}
})(Chip);
export default function SmallChips() {
const classes = useStyles();
const handleClick = () => {
console.info("You clicked the Chip.");
};
return (
<div className={classes.root}>
<StyledChip variant="outlined" size="small" label="Basic" />
<StyledChip
size="small"
variant="outlined"
avatar={<Avatar>M</Avatar>}
label="Clickable"
onClick={handleClick}
/>
</div>
);
}
<ThemeProvider theme={theme}>
I am using a theme provider, and I want to use two themes on the same DOM element.
style={theme.flexRowLeft}
I want to use two themes at the same time, but right now I can only use one element, and I have no idea how to do what I want to do.
const theme = createMuiTheme({
flexRow: {
display: "flex",
flexDirection: "row",
alignItems: "center",
},
justifyRight: {
justifyContent: "right",
},
});
How do I combine flexRow and justifyRight?
I am using Material-UI's themeprovider.
As suggested here
assuming you got theme through the useTheme hook inside your functional component:
const theme = useTheme()
you can try string interpolation:
<div className={`${theme.flexRow} ${theme.justifyRight}`}/>
so in total, for example:
const someComponent = props => {
const theme = useTheme();
return(<div className={`${theme.flexRow} ${theme.justifyRight}`}/>);
}
note that you should use className property, and not the style!
In one file you create files with themes like themes.js
where you put:
export const theme = createMuiTheme({
flexRow: {
display: "flex",
flexDirection: "row",
alignItems: "center",
},
justifyRight: {
justifyContent: "right",
},
});
And when you want to use it in functional component you write something like:
import clsx from "clsx";
import { makeStyles } from "#material-ui/core/styles";
const myStyles = makeStyles(theme => ({
AA: {
display: theme.flexRow.display,
flexDirection: theme.flexRow.flexDirection,
alignItems: theme.flexRow.alignItems
},
BB: {
justifyContent: theme.justifyRight.justifyContent
}
}), {name: "StyleNameVisibleInCss"});
function myFunctionalComponent() {
const classes = myStyles();
return (
<div className={clsx(classes.AA, classes.BB)}> Some text </div>
)
}
// react-native example
import { StyleSheet, View } from 'react-native';
const styles = {
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
}
}
const stylesRN = StyleSheet.create(styles);
<View style={stylesRN.container}></View>
What the best way to reuse
// inner styles
{
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
}
in both react-native and react?
What i want to achieve in pseudocode (or another way of reuse in React):
<div style={magicAdapter(styles.container)}>Hello World!</div>
Problem: It is impossible to reuse all react-native inline-styles in react as is without magicAdapter.
What you could do is store all your styles in an object in some file e.g. const containerStyles = { borderRadius: 2 }, export it, then for React Native use the StyleSheets javascript class to create the styles for your div container
import {containerStyles} from '../someFile.js'
const styles = StyleSheets.create({
container: containerStyles
})
then for React you could do inline styling with the same object, but be aware that not all styles supported in StyleSheets can be used for inline styling, so if you want to do something equivalent there's libraries out there like emotion.js to dynamically load CSS in JS
https://github.com/emotion-js/emotion
Heres an example
import {css} from 'emotion'
import {containerStyle} from '../someFile'
const getContainerStyles = css`
border-radius: ${containerStyle.borderRadius}
`
export default class SomeClass extends Component {
render() {
return(
<div
style={getContainerStyles}
>
</div>
)
}
}
I hope this helps
You could concatenate the style of your new component with the style of container, like below
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
newComponent:{
// New component style
}
});
<View style={[styles.container, styles.newComponent]}>
</View>
// your component file name (button.js)
import React, { Component } from 'react';
// import the style from another file present in the same directory
import styles from 'button.style.js';
// you can reuse this style in another component also
class Button extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.buttonText}> Press Me! </Text>
</View>
);
}
}
export default Button;
// your style file name ( "button.style.js")
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
padding: 10,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#43a1c9',
},
buttonText: {
fontSize: 20,
textAlign: 'center'
}
});