Cascade text color with Styled Components on React Native - react-native

I'm trying to create a component with React Native that includes a <Text> component inside the wrapped component:
const MyComponent = props => (
<View {...props}>
<Text {...props}>Hello world</Text>
<View>
)
const MyRedComponent = styled(MyComponent)`
color: #000;
margin-left: 10px;
background: #F00;
`
I'm composing my component this way so I could change the the text color from the same styled component as I'm changing the background color:
const MyBlueComponent = styled(MyRedComponent)`
color: #FFF;
background: #00F;
`
However with this approach there is a problem that all of the styles get applied to the <Text> component, not only the color; in this example the <Text> component also gets the margin-left style from the parent styles which is not preferred. I'd only like the text color to be cascaded to the <Text> component.
Is this possible using Styled Components with React Native?

You can create a wrapper function using StyleSheet.flatten and pick the color from the resulting object:
const MyComponent = props => (
<View {...props}>
<Text style={{ color: StyleSheet.flatten(props.styles).color }}>Hello world</Text>
<View>
)
const MyRedComponent = styled(MyComponent)`
color: #000;
margin-left: 10px;
background: #F00;
`
It makes sense to extract the picking to its own function. For example you could create the following wrapper:
const pickColorFromStyles = styles => {
const { color } = StyleSheet.flatten(styles)
return { color }
}
...and use that function in your component:
const MyComponent = props => (
<View {...props}>
<Text style={pickColorFromStyles(props.styles)}>Hello world</Text>
<View>
)
Notice the warning with using StyleSheet.flatten from the documentation page:
NOTE: Exercise caution as abusing this can tax you in terms of optimizations. IDs enable optimizations through the bridge and memory in general. Refering to style objects directly will deprive you of these optimizations.

Related

horizontal scrollview inside react native drawer

I was asked to add an horizontal scrollview inside of a drawer. (Think Instagram stories but in a drawer.) The animation works very inconsistent. The most annoying is swiping to the left, the scrollview often doesn't scroll and the drawer starts the closing animation instead.
The drawer already exists for awhile, so setup issues are not the problem.
Here is my code:
// Drawer.navigator
const DrawerContent = () => {
// UI of the drawer
return <Drawer activeRoute={activeRoute} />;
};
return (
<DrawerNav.Navigator
drawerContent={DrawerContent}
drawerStyle={{ width: 320 }}
initialRouteName="Home"
drawerContentOptions={{
someColor: theme.someColor,
}}
>
// other screens
</DrawerNav.Navigator>
import { ScrollView } from 'react-native';
const Container = styled.View`
height: 62.5px;
flex-direction: row;
align-items: center;
justify-content: center;
`;
const ThingWrapper = styled(Flex)`
height: 62.5px;
align-items: center;
justify-content: center;
.lastBubble {
margin-right: 16px;
}
`;
...
<Container>
<ScrollView
horizontal
vertical={false}
directionalLockEnabled
nestedScrollEnabled
showsHorizontalScrollIndicator={false}
pinchGestureEnabled={false}
contentContainerStyle={{
zIndex: 9999,
}}
>
{thingsToRender.map((thing, index) => {
return (
<ThingWrapper>
<AnyThing thingId={thing.id} />
</ThingWrapper>
);
})}
</ScrollView>
</Container>
I think element focus might be the main problem here, but I don't know how to fix this. Any help is greatly appreciated. Thanks!
I fixed it by creating this object and giving it as a param to every screen. As a con this disable the drawer swipe open/close.
const drawerScreenOptions = {
gestureEnabled: true,
swipeEnabled: false,
};
// Drawer.navigator
const DrawerContent = () => {
// UI of the drawer
return <Drawer activeRoute={activeRoute} />;
};
return (
<DrawerNav.Navigator
drawerContent={DrawerContent}
drawerStyle={{ width: 320 }}
initialRouteName="Home"
>
// !! Give options to all screens
<DrawerNav.Screen
name="screenX"
component={ScreenXNavigator}
options={drawerScreenOptions}
/>
</DrawerNav.Navigator>

Separator style for header and body in FlatList

Currently, I'm having a problem with FlatList.
I have a component to render a list book.
By design, the header's width is the width of the screen and the body will be padding left and right 10px.
So I used contentContainerStyle={{paddingHorizontal: 10}}.
But The result is that the header and body are 10px padding left and right.
Please suggest a way to resolve it. Sorry for my bad English!!
Update: I'm so sorry for not describing my problem thoroughly.
In main.tsx
...
public render() {
return (
<FlatList
key...
data={..}
renderItem={this.renderItems}
ListHeaderComponent={this.renderHeader}
contentContainerStyle={styles.contentStyle}
/>
);
}
private renderHeader = () => {
return (
<View style={style.header}
//TODO something ...
</View>
);
}
private renderItems: ListRenderItem<IBook> = ({ item: {bookId} }) => bookId ?
(
<BookGridCell
title={...}
image={...}
//TODO more..
/>
) : <View style={styles.emptyBox} />
}
At renderItems, I called a component BookGridCell. In this component, the design of a book is set up. So if I directly add style inside renderItems, each book will have a left and right margin of 10px, not the entire body.
When use contentContainerStyle
with contenContainerStyle
When directly add style inside renderItems
with not use contentContainerStyle
Give a style to your body.
style={styles.bodyContainer}
and then inside StyleSheet add property.
const styles = StyleSheet.create({
bodyContainer: {
paddingHorizontal: 10
},
This is the correct way or
you can directly add padding inside your View.
style={{ paddingHorizontal: 10 }}

Applying styles in react native with styled components

I have a component, Logo, where I'm trying to apply a width and a height:
import styledNative from "styled-components/native";
import {Image, View} from "react-native"
import companyIcon from "../svg/companyIcon.svg";
const Logo = () => (
<Image source={{uri: companyIcon}} />;
);
const StyledLogo = styledNative(Logo)`
width: 48px;
height: 48px;
`;
const Header = () => {
return (
<View>
<StyledLogo />
</View>
);
};
This leaves us with an invisible logo, with a width of 0. Styled-components docs seems to suggest using a passed in className property (https://www.styled-components.com/docs/advanced#existing-css), however this seems to be for standard styled-components, not react-native / react-native-web implementation, because it's just not passed to my component. When I examine the passed props for Logo (by console logging them), there is a style object, but this doesn't appear to be anything I can use and there's not much mention of it in the styled component documentation:
style: Array(2)
0: 91
1: undefined
length: 2
So I'm not really sure what to do with it. How can components be styled by styled-components?
You aren't applying your styles to the image.
What is happening now, is that your Logo component is receiving those style-props, but it's not passing them through.
const Logo = (props) => ( // <-- containing style props
<Image source={{uri: companyIcon}} />;
);
So what you would want to do is to spread your Logos props over your Image, so it gets the data it needs.
const Logo = (props) => ( // <-- containing style props
<Image {...props} source={{uri: companyIcon}} />; // <-- styles now applied to Image
);
Or alternatively you can pass through just the style-props.
const Logo = (props) => ( // <-- containing style props
<Image source={{uri: companyIcon}} style={props.style}/>;
);
Edit:
It also appears you are passing invalid styles. React Native expects the values you pass in to not have units. As such, your styles should be width: 48; instead, dropping the ...px.

React Native - FlatList Not Rendering

(Note: I'm using Expo for this app)
I'm attempting to render a FlatList that displays a list of printers. Here's the code:
<FlatList
data={printers}
keyExtractor={printer => printer.id}
renderItem={({ item }) => {
return (
<Printer
printerTitle={item.name}
selected={item.selected}
last={item === last(printers)}
/>
);
}}
/>
Here's the code for the <Printer /> component:
const Printer = props => {
const { last, printerTitle, selected } = props;
return (
<View style={[styles.container, last ? styles.lastContainer : null]}>
<View style={styles.innerContainer}>
<View style={styles.leftContainter}>
{selected ? (
<Image source={selected ? Images.checkedCircle : null} />
) : null}
</View>
<View style={styles.printerDetails}>
<Text style={styles.printerTitle}>{printerTitle}</Text>
</View>
</View>
</View>
);
};
...
export default Printer;
I can't seem to get the <Printer /> component to render. I have tried including a similar custom component (that has worked in a FlatList in another part of the app) in the renderItem prop, and it doesn't work either.
However, when I replace the <Printer /> component with <Text>{item.name}</Text> component, the printer name renders like I would expect it to.
Has anyone run into this issue before, or does anyone have a solution?
In my case, where I'm rendering a custom component for each item in the list, it wasn't rendering because I accidentally had {} surrounding the return part of the renderItem prop instead of ().
Changing:
<FlatList
data={array}
renderItem={({item, index}) => { <CustomItemComponent /> }}
/>
to this:
<FlatList
data={array}
renderItem={({item, index}) => ( <CustomItemComponent /> )}
/>
Solved my issues.
I suspect there are two issues at hand: one is that your FlatList is not filling the screen (namely its parent view) and the other is that your Printer component is not being sized correctly.
For the first issue, put a style with { flex: 1 } on your FlatList. This way it will fill its parent view.
For the second issue, try adding { borderColor: 'red', borderWidth: 1 } to your Printer components, just so that you can more easily see where they're being rendered. If they seem like they have no width, make sure you haven't overridden alignSelf on the Printer component's root view. If they seem like they have no height, add height: 100 temporarily just so you can see what the contents of the Printer components look like.
Within your Printer component, make sure to specify the width and height of your image on the Image component like { width: 40, height: 30 } or whatever the dimensions of your image is in logical pixels.
I have same problem.
Resolve with adding width to FlatList
render() {
const dimensions = Dimensions.get('window');
const screenWidth = dimensions.width;
return(
<FlatList
style={{
flex: 1,
width: screenWidth,
}}
... some code here
/>
)
}
You can't use the keyExtractor in this way, make this function like below. It might solve your problem.
_keyExtractor = (item, index) => index;
If you update your question with you printer component code we can help you better.
In my case I accidentally made it a pair tag: <FlatList></FlatList>, which for some reason breaks rendering of list items.
in my case Container was not having width of 100%:
const Container = styled.View`
border: 1px solid #ececec;
margin-top: 43px;
padding-top: 36px
padding-bottom: 112px;
width: 100%;
`;
const StyledFlatList = styled(
FlatList as new () => FlatList<SimilarRewards_viewer['merchants']['edges'][0]>
)`
width: 100%;
height: 150px;
flex: 1;
padding-left: 15px;
padding-right: 15px;
`;

Want to change opacity with react native refs on click

Here is my code. I want to change the opacity of refs when i click on any TouchableOpacity component.Please guide me how i can change opacity or change colour in react native with refs.
When i click my redirect function calls so i wanna change the opacity of particular ref in redirect function, i am passing ref and routename is redirect function.
i
mport React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet
} from 'react-native';
export default class Navigation extends Component {
redirect(routeName,ref)
{
console.log(this.refs[ref]]);
this.props.navigator.push({
ident: routeName
});
}
render() {
return (
<View style={style.navigation}>
<View style={[style.navBar,styles.greenBack]}>
<TouchableOpacity style={style.navPills} onPress={ this.redirect.bind(this,"AddItem","a")} ref="a">
<Text style={[style.navText,style.activeNav]}>HOME</Text></TouchableOpacity>
<TouchableOpacity style={style.navPills} onPress={ this.redirect.bind(this,"AddItem","b")} ref="b">
<Text style={style.navText}>ORDER</Text></TouchableOpacity>
<TouchableOpacity style={style.navPills} onPress={ this.redirect.bind(this,"ListItem","c")} ref="c">
<Text style={style.navText}>SHOP LIST</Text></TouchableOpacity>
<TouchableOpacity style={style.navPills} onPress={ this.redirect.bind(this,"ListItem","d")} ref="d">
<Text style={style.navText}>DUES</Text></TouchableOpacity>
</View>
<View style={style.titleBar}>
<Text style={style.titleBarText}>{this.props.title}</Text>
</View>
</View>
);
}
}
const style = StyleSheet.create({
navigation:{
top:0,
right:0,
left:0,
position:'absolute'
},
navBar:{
flexDirection:'row',
padding:10,
paddingTop:15,
paddingBottom:15,
},
navPills:{
flex:1,
alignItems:'center'
},
navText:{
flex:1,
textAlign:'center',
fontSize:16,
fontWeight:'bold',
color:'#ffffff',
opacity:0.7
},
titleBar:{
backgroundColor:'#ffffff',
flex:1,
padding:8,
alignItems:'center',
borderBottomWidth:1,
borderBottomColor:'#dddddd'
},
titleBarText:{
fontSize:18
},
activeNav:{
opacity:1
}
});
I am not exactly sure if the following is what u are searching:
If you want to change the opacity of the TouchableOpacity use the following
export default class Navigation extends Component {
state={
opacity: 0.1
}
handleOnPress = () => {
this.setState({
opacity: 0.5 //Anything u want
});
}
render(){
return(
<TouchableOpacity underlayColor={'rgba(0,0,0,this.state.opacity)'} onPress={this.handleOnPress}>
)
}
}
If you want to change the opacity of your text use the following
export default class Navigation extends Component {
state = {
opacity: 0.1
}
handleOnPress = () => {
this.setState({
opacity: 0.5 //Anything u want
});
}
render(){
return(
<TouchableOpacity onPress={this.handleOnPress}>
<Text style={[style.navText, {opacity: this.state.opacity}]}>DUES</Text>
</TouchableOpacity>
)
}
}
Using the Stylemethods in the render allows you to take variables from the state
Hope this is the answer you wanted. If One of both is the right let me know and i delete the other one.
Best Regards
Put your opacity value into state. Then make the button click change the value of that state. This will trigger a re-render and your view will update with the new opacity.
To expand on the answer from pomo...
With the styles as you currently have them, you can easily call setState within each of your onPress functions to change the opacity of the elements you need changed. You don't even need to pass a reference if you utilize a different key in the state for each item.
Then, in your styles you would use an array of styles to use the opacity value from the state.
style={[style.navPills, { opacity: this.state.opacityA }]}
I'm not a fan of inline styles at all. So, for my purposes in a recent project I set the style of an element using its 'ref' value, then triggered a state change merely to cause the render function to be called. This is what I believe you're asking for and this sample code should point you in the right direction, otherwise perhaps this will help someone else in the future.
toggleDisplay() {
if (this.refs.blah.style.display === "") { // currently visible
this.refs.blah.style.display = "none";
this.setState({showBlah = false});
} else { // currently not visible
this.refs.blah.style.display = "";
this.setState({showBlah: true});
}
}
render() {
// Some element defined with the ref value used above.
return (<div>
<div ref="blah">Now you see me...</div>
<button onClick="this.toggleDisplay">Toggle Me</button>
</div>);
}
Nothing in my render function changed by adding the toggle functionality, other than adding a button somewhere to call the function. As I already indicated, that state value is only used to trigger the render process.