This is react-native question but similar concepts can be applied to react.
I want to create a CustomView in react-native. I am using typescript.
So far, I have:
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff',
borderRadius: 10,
}
});
type CustomViewProps= {
width: number,
height: number,
marginTop?: string | number,
}
const CustomView = ({ width, height, marginTop }: CustomViewProps) => (
<View style={[styles.container, { height, width, marginTop }]} />
);
This is ok so far because only 3 props are being used: width, height and marginTop.
However, this is not reusable and it can become verbose if I need to add many more props.
So, the question is: How can I make CustomView receive any props as a native component View could receive?
My guess is I should delete CustomViewProps. Then, I should make the props inherit from the same type that the native component View does. However, I am struggling with it.
Since you are creating CustomViewProps, I assume that you want to add some specific behaviours to your native component above the already written behaviours of that component.
Let's create an example.
I want to create a button with some specific behaviours but i want it to behave, in other cases, like a normal TouchableOpacity component. For example, i want to add a "loading" state which will show a loader inside instead of its content.
So the logic is: create your custom props and merge you custom props with native's default props
import React, { FC, ReactElement } from 'react'
import { ActivityIndicator, TouchableOpacity, TouchableOpacityProps, StyleSheet } from 'react-native'
type MyProps = {
loading?: boolean
children: ReactElement
}
const MyButton: FC<MyProps & TouchableOpacityProps> = (props: MyProps & TouchableOpacityProps) => (
<TouchableOpacity {...props} disabled={props.disabled || props.loading} style={[styles.button, props.style]}>
{props.loading ? <ActivityIndicator /> : props.children}
</TouchableOpacity>
)
const styles = StyleSheet.create({
button: {
backgroundColor: 'yellow',
borderColor: 'black',
borderWidth: 1,
borderRadius: 10,
padding: 10
},
})
export default MyButton
The loading prop will be responsible for both content of the button or is disabled prop. The TouchableOpacity component will receive every compatible prop (autosuggest will be enabled because you have assigned the TouchableOpacityProps). The styles.button will behave like default style but will be overwritten if you specify something different in your style prop. That's it!
Related
I'm creating some custom components for my application and they essentially have some base styling done to them for light/dark modes. My goal is to be able to use those components with all their props later when the custom component is being used to stay flexible. How do I achieve this?
For example if I style a custom input component and use it, I want to be able to tap into the secureTextEntry prop when needed. Here is an example of what I have right now for my CustomText. I want to be able to style this further when needed.
import { Text, useColorScheme } from 'react-native';
import React from 'react';
type CustomTextProps = {
text: string;
};
const CustomText = ({ text }: CustomTextProps) => {
const isDarkMode = useColorScheme() === 'dark';
return <Text style={{ color: isDarkMode ? '#fff' : '#000' }}>{text}</Text>;
};
export default CustomText;
react-native expose interfaces for each component.
so you need to extend your interface with TextProps:
import { Text, TextProps } from 'react-native';
interface CustomTextProps extends TextProps {
text: string;
};
By extending those interfaces (e.g. TextProps) in CustomTextProps we can have all text element props passed to this component.
Instead of having to declare each one we can just use a spread attribute ...rest
const CustomText = ({ text, ...rest }: CustomTextProps) => {
const isDarkMode = useColorScheme() === 'dark';
return <Text style={{ color: isDarkMode ? '#fff' : '#000' }} {...rest}>{text}</Text>;
};
export default CustomText;
This sounds like a job for React Context which acts as a store for global state that you can access using useContext hook
The Goal:
To create a reusable react component and have the flexibility to style any part of the component in the future.
for example, I create a button-like component that I want to reuse in different cases in the future and give it different styles in the different places that I use it:
function reusableButton(){
return(
<View style={styles.defaultStyle}>
<Text style={styles.defaultTitleStyle}>Button Title</Text>
</View>
)}
const styles = StyleSheet.create({
defaultStyle:{
height: 50,
width: 100,
borderRadious: 25,
backgroundColor:'red'},
defaultTitleStyle: {
color:'green',
fontWeight: 'bold'}
})
Question is: How do I make changes to the default styles in the future when I use this button?
The Goal:
To create a reusable react component and have the flexibility to style any part of the component in the future.
How to achieve this:
Create the reusable component using const or function.
Pass 'props' as a parameter to the component, whether functional or const.
Give the elements of your reusable component stylings that are arrays of the default styling and "props.futureStyle". Note that "futureStyle" here is just a name and you can use any name you want.
Whenever you call the reusable component, you can easily use "futureStyle" to make any changes. Note that you can declare "futureStyle1", "futureStyle2" etc to the different parts of the reusable components so that you can edit the styles of every View, Text, etc that is part of the components wherever they may be added in the future
Example:
Creating the reusable components:
const ReusableButton = (props) =>{
return(
<View style={[styles.currentButtonStyle, props.futureButtonStyle]}>
<Text style={[styles.currentButtonTitle, props.futureTitleStyle]}>{props.title}</Text>
</View> )};
//Here are my default styles:
const styles = Stylesheet.create({
currentButtonStyle: {
height:50,
width: 100,
backgrroundColor:'red'},
currentButtonTitle:{
color: 'white'
fontSize: 20,
},
})
Henceforth, anywhere I wish to call and use the ReusableButton, it can edit the styles with the future styles.
Example:
function NewComponent(){
return(
<View>
<Text>Hi let us make use of our old reusable button</Text>
<ReusableButton futureButtonStyle={{width: 200, height: 100, backgroundColor:'blue'}} futureTitleStyle={{fontWeight:'bold', fontSize: 50}}/>
</View>
)
}
Using MUI V5, how can I pass a custom style to a button component? Here is what I have been trying to merge the old way with the new MUI v5 but it's not working.
import { Button } from "#mui/material";
import { styled } from "#mui/material/styles";
import React from "react";
const StyledButton = styled(Button)(({ theme }) => ({
root: {
minWidth: 0,
margin: theme.spacing(0.5),
},
secondary: {
backgroundColor: theme.palette.secondary.light,
"& .MuiButton-label": {
color: theme.palette.secondary.main,
},
},
primary: {
backgroundColor: theme.palette.primary.light,
"& .MuiButton-label": {
color: theme.palette.primary.main,
},
},
}));
export default function ActionButton(props) {
const { color, children, onClick } = props;
return (
<Button
className={`${StyledButton["root"]} ${StyledButton[color]}`}
onClick={onClick}
>
{children}
</Button>
);
}
Now I would like to call this Button and give it color="secondary"
import ActionButton from "./ActionButton";
import { Close } from "#mui/icons-material";
export default function Header() {
return (
<ActionButton color="secondary">
<Close />
</ActionButton>
)
}
It looks like your code was an attempt to migrate from code using makeStyles/useStyles, but styled works quite a bit differently. You can't use it to generate multiple CSS classes like makeStyles does (StyledButton["root"] and StyledButton[color] will be undefined). styled will generate a single CSS class that is then passed in the className prop to the wrapped component (e.g. Button). Instead of trying to create multiple CSS classes with logic to decide which class to apply, with styled you can leverage props (e.g. the color prop) to generate dynamic styles within the single CSS class that is generated.
Below is an example that I think achieves the effect your code was aiming for. My example doesn't do anything with MuiButton-label because that class doesn't exist in v5 (and the <span> that the class was applied to inside the <button in v4 also does not exist), and I believe the default Button styles set color as desired when the color prop is allowed to passed through to Button.
import Button from "#mui/material/Button";
import { styled } from "#mui/material/styles";
const StyledButton = styled(Button)(({ theme, color }) => ({
minWidth: 0,
margin: theme.spacing(0.5),
backgroundColor: color ? theme.palette[color].light : undefined
}));
export default StyledButton;
I am trying to create a slider menu with the react-native-drawer-menu module. After installing the module . get an error can`t find variable styles. This is the code copied from the example:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux'
import'../I18n/I18n.js';
import RootContainer from './RootContainer'
import createStore from '../Redux'
import Drawer from 'react-native-drawer-menu';
import {Easing} from 'react-native'; // Customize easing function (Optional)
// create store
const store = createStore()
export default class App extends React.Component {
render() {
// prepare your drawer content
var drawerContent = (<View style={styles.drawerContent}>
<View style={styles.leftTop}/>
<View style={styles.leftBottom}>
<View><Text>Drawer Content</Text></View>
</View>
</View>);
var customStyles = {
drawer: {
shadowColor: '#000',
shadowOpacity: 0.4,
shadowRadius: 10
},
mask: {}, // style of mask if it is enabled
main: {} // style of main board
};
return (
<Drawer
style={styles.container}
drawerWidth={300}
drawerContent={drawerContent}
type={Drawer.types.Overlay}
customStyles={{drawer: styles.drawer}}
drawerPosition={Drawer.positions.Right}
onDrawerOpen={() => {console.log('Drawer is opened');}}
onDrawerClose={() => {console.log('Drawer is closed')}}
easingFunc={Easing.ease}
>
<View style={styles.content}>
<Text>{Object.values(Drawer.positions).join(' ')}</Text>
<Text>{Object.values(Drawer.types).join(' ')}</Text>
</View>
</Drawer>
);
}
}
If I delete the variable from the code then the slide menu works but looks extremely bad.
Do you think that I am supposed to create the style of the menu on my own or shall I imported from somewhere? If I have to create it, how can I know which parameters did it take? Or is it a normal view?
Looks like you have to add styles by yourself to make look Drawer content exactly as you want to. To achieve it you have to create Stylesheet
You can use this answer to get more info about React Native Stylesheet properties (it's pretty much similar to css)
Also maybe this example from drawer repo would be helpful
Cheers.
After starting to work with React.js, it seems like props are intended to be static (passed in from the parent component), while state changes based upon events. However, I noticed in the docs a reference to componentWillReceiveProps, which specifically includes this example:
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
This seems to imply that the properties CAN change on a component based upon the comparison of nextProps to this.props. What am I missing? How do props change, or am I mistaken about where this gets called?
A component cannot update its own props unless they are arrays or objects (having a component update its own props even if possible is an anti-pattern), but can update its state and the props of its children.
For instance, a Dashboard has a speed field in its state, and passes it to a Gauge child thats displays this speed. Its render method is just return <Gauge speed={this.state.speed} />. When the Dashboard calls this.setState({speed: this.state.speed + 1}), the Gauge is re-rendered with the new value for speed.
Just before this happens, Gauge's componentWillReceiveProps is called, so that the Gauge has a chance to compare the new value to the old one.
PROPS
A React component should use props to store information that can be
changed, but can only be changed by a different component.
STATE
A React component should use state to store information that the
component itself can change.
A good example is already provided by Valéry.
Props can change when a component's parent renders the component again with different properties. I think this is mostly an optimization so that no new component needs to be instantiated.
Much has changed with hooks, e.g. componentWillReceiveProps turned into useEffect+useRef (as shown in this other SO answer), but Props are still Read-Only, so only the caller method should update it.
Trick to update props if they are array :
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button
} from 'react-native';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: this.props.count
}
}
increment(){
console.log("this.props.count");
console.log(this.props.count);
let count = this.state.count
count.push("new element");
this.setState({ count: count})
}
render() {
return (
<View style={styles.container}>
<Text>{ this.state.count.length }</Text>
<Button
onPress={this.increment.bind(this)}
title={ "Increase" }
/>
</View>
);
}
}
Counter.defaultProps = {
count: []
}
export default Counter
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
If you use recompose, use mapProps to make new props derived from incoming props
Example:
import { compose, mapProps } from 'recompose';
const SomeComponent = ({ url, onComplete }) => (
{url ? (
<View />
) : null}
)
export default compose(
mapProps(({ url, storeUrl, history, ...props }) => ({
...props,
onClose: () => {
history.goBack();
},
url: url || storeUrl,
})),
)(SomeComponent);