import { memo } from 'react';
import classNames from 'classnames/bind';
import styles from './Button.module.scss';
const cn = classNames.bind(styles);
const Button = memo(
({ design: { size, color, outline, hover }, content, type, onClick }) => (
<button
type={type}
className={cn('button', size, color, { outline }, { hover })}
onClick={onClick}
>
{content}
</button>
)
);
Button.defaultProps = {
size: 'md',
color: 'black',
};
export default Button;
defaultProps doesn't work in this case. If I set default value when destructuring props like:
{ design: { size='md', color='black', outline, hover }, content, type, onClick }, it works.
I passed the props:
<Button
design={{ size: 'sm', outline: true }}
content="Edit"
onClick={onEditClick}
/>
What's wrong here?
Button.defaultProps = {
size: 'md',
color: 'black',
};
You've defined defaultProps for size and color, but your component doesn't actually use a size or color prop. You do have a design prop, so you could provide a default value for that:
Button.defaultProps = {
design: { size: 'md', color: 'black' }
}
But this will only have an effect if design is undefined. defaultProps will not recurse to nested properties of objects. If you pass an object to design, even an object that doesn't have a color, react will see that the prop is not undefined, and so it won't use the default props.
If you need to fill in default values for nested object properties, then you will need to write the code for that yourself. For example, the destructuring code you wrote.
I'm trying to overwrite the default CSS of an external component which isn't developed in Material-UI or my project. In styled-components, I can just take the root classes and replace them with my custom CSS. How do I do the same with Material-UI-React?
.ace-tm .ace_variable {
color : red
}
Suppose I've to replace those two classes with the new color property, how do I do it in Material styles?
This is what I've tried with no luck!
const Styles = {
" & ace-tm": {
"& ace_variable": {
color: red,
fontSize: "16px"
},
}
};
I'm using withStyles to later inject them in the components.
I just found this and thought I'd share the solution for posterity:
const GlobalCss = withStyles((theme) => ({
'#global': {
'.ace-tm .ace_variable': {
color: 'red',
},
},
}))(() => null)
const SomeComponent = () => {
return (
<>
<GlobalCss />
<h1>Hey Jude</h1>
<SomeComponentWhoseCSSWillBeModified />
</>
}
Read more on this here: https://material-ui.com/styles/advanced/#global-css
I have ReactJS project and I want to change colour of button during clicking. I know that it is a Ripple API but it's very incomprehensible to use it. Could someone advise me how can I do that?
I've tried to create two elements - parent and child - and changed background of child to transparent while clicking. Unfortunately I have also 'classes' object responsible for changing class if button is active and it is just not working.
My code below:
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import PropTypes from 'prop-types';
import styles from './MydButton.style';
class MyButton extends Component {
constructor(props) {
super(props);
this.state = {
isClicked: false
};
}
handleClick = () => {
this.setState({ isClicked: !this.state.isClicked });
}
render() {
const {
classes,
children,
color,
disabled,
className,
onClick,
type,
border,
...props
} = this.props;
const myClass = this.state.isClicked ? 'auxClass' : 'buttonDefaultRoot';
return (
<div className={classes.parentRoot} >
<Button
classes={{
root: disabled
? classes.buttonDisabledRoot
: classes.buttonRoot,
label: disabled
? classes.buttonLabelDisabled
: classes.buttonLabel,
}}
{...props}
onClick={this.handleClick}
className={myClass}
disabled={disabled}
type={type === undefined ? 'button' : type}
>
{children}
</Button>
</div>
)
}
};
MyButton.propTypes = {
children: PropTypes.string.isRequired,
disabled: PropTypes.bool,
classes: PropTypes.object.isRequired,
};
MyButton.defaultProps = {
disabled: false,
};
export default withStyles(styles)(MyButton);
and styles:
const buttonRoot = {
border: 0,
height: 48,
width: '100%',
}
export default theme => ({
buttonDefaultRoot: {
...buttonRoot,
transition: 'all 1s ease-in-out',
backgroundImage: 'linear-gradient(to right, #F59C81, #E65DA2, #E65DA2, #B13A97, #881E8E)',
boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.16)',
backgroundSize: '300% 100%',
marginTop: 0,
'&:hover': {
backgroundPosition: '100% 0%',
transition: 'all 1s ease-in-out',
}
},
parentRoot: {
...buttonRoot,
backgroundColor: 'red',
backgroundSize: '300% 100%',
marginTop: 36,
},
auxClass: {
backgroundImage: 'none',
},
Material UI Core for ReactJS
The documentation is very good. I have updated my answer to accomodate the specific needs of this question. I have also included two general solutions for anyone who stumbles upon this question.
Tailored Solution:
Changes background color of button from classes.buttonDefaultRoot (a color defined by owner of question) to the gradient defined by the owner of this question.
First step, have a variable stored in state. You can call it whatever you want, but I'm calling bgButton. Set this to this.props.classes.buttonDefaultRoot like so:
state = {
bgButton: this.props.classes.buttonDefaultRoot,
}
Next, you want to define your function that will handle the click. Again, call it what you want. I will call it handleClick.
handleClick = () => {
const { classes } = this.props; //this grabs your css style theme
this.setState({ bgButton: classes.parentRoot.auxClass }); //accessing styles
};
A couple of things are happening here. First, I am destructuring props. So, I am creating a new const variable called classes that has the same value as this.props.classes. The classes contains a set of objects that defines your css styles for your buttons, margins, etc. You can access those styles just like you would if you were trying to get the value of a prop in an obj.
In this case you can access your button style by doing, classes.buttonDefaultRoot. That takes care of your handle click function.
Last step: render the button. In your render method you want to grab your bgButton from state like so:
render() {
const { bgButton } = this.state;
Then you want to assign your className of your button to bgButton and add the onClick functionality like this (this follows the Material UI Core documentation):
<Button variant="contained" color="primary" className={classNames(bgButton)} onClick={this.handleClick}>Button Name</Button>
Putting it all together you get this:
import React, { Component } from "react";
import Button from "#material-ui/core/Button";
import PropTypes from "prop-types";
import classNames from "classnames";
import { withStyles } from "#material-ui/core/styles";
export default theme => ({ ... }) //not going to copy all of this
class MyButton extends Component {
state = {
bgButton: null
};
handleClick = () => {
const { classes } = this.props;
this.setState({ bgButton: classes.parentRoot.auxClass });
};
render() {
const { bgButton } = this.state;
return (
<div className={classes.container}>
<Button
variant="contained"
color="primary"
className={classNames(bgButton)}
onClick={this.handleClick}
>
Custom CSS
</Button>
</div>
);
}
}
MyButton.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(MyButton);
General Solution
This solution is for those who want to use the predefined colors, i.e. default, primary, secondary, inherit. This implementation does not need the PropTypes or className imports. This will change the color from the predefined blue to the predefined pink. That's it.
state = {
bgButton: "primary",
}
handleClick = () => {
this.setState({ bgButton: "secondary" });
}
render() {
const { bgButton } = this.state;
return(
...
<Button
onClick = {this.handleClick}
variant = "contained" //checked Material UI documentation
color={bgButton}
> ..etc.
General Solution 2
To accommodate your custom styles to the button, you would have to import PropTypes and classNames and take a similar approach as the tailored solution above. The only difference here will be my syntax and class name. I am closely following the documentation here so you can easily follow along and readjust where necessary.
import React, { Component } from "react";
import Button from "#material-ui/core/Button";
import PropTypes from "prop-types";
import classNames from "classnames";
import { withStyles } from "#material-ui/core/styles";
import purple from "#material-ui/core/colors/purple";
const styles = theme => ({
container: {
display: "flex",
flexWrap: "wrap"
},
margin: {
margin: theme.spacing.unit
},
cssRoot: {
color: theme.palette.getContrastText(purple[500]),
backgroundColor: purple[500],
"&:hover": {
backgroundColor: purple[700]
}
},
bootstrapRoot: {
boxShadow: "none",
textTransform: "none",
fontSize: 16,
padding: "6px 12px",
border: "1px solid",
backgroundColor: "#007bff",
borderColor: "#007bff",
fontFamily: [
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
"Roboto",
'"Helvetica Neue"',
"Arial",
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"'
].join(","),
"&:hover": {
backgroundColor: "#0069d9",
borderColor: "#0062cc"
},
"&:active": {
boxShadow: "none",
backgroundColor: "#0062cc",
borderColor: "#005cbf"
},
"&:focus": {
boxShadow: "0 0 0 0.2rem rgba(0,123,255,.5)"
}
}
});
class MyButton extends Component {
state = {
bgButton: null
};
handleClick = () => {
const { classes } = this.props;
this.setState({ bgButton: classes.cssRoot });
};
render() {
const { classes } = this.props; //this gives you access to all styles defined above, so in your className prop for your HTML tags you can put classes.container, classes.margin, classes.cssRoot, or classes.bootstrapRoot in this example.
const { bgButton } = this.state;
return (
<div className={classes.container}>
<Button
variant="contained"
color="primary"
className={classNames(bgButton)}
onClick={this.handleClick}
>
Custom CSS
</Button>
</div>
);
}
}
MyButton.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(MyButton);
A tip. You no longer need a constructor or to bind methods.
Hope this helps.
I'm developing a React Native application to be deployed as a native application on iOS and Android (and Windows, if possible).
The problem is that we want the layout to be different depending on screen dimensions and its orientation.
I've made some functions that return the styles object and are called on every component render's function, so I am able to apply different styles at application startup, but if the orientation (or screen's size) changes once the app has been initialized, they aren't recalculated nor reapplied.
I've added listeners to the top rendered so it updates its state on orientation change (and it forces a render for the rest of the application), but the subcomponents are not rerendering (because, in fact, they have not been changed).
So, my question is: how can I make to have styles that may be completely different based on screen size and orientation, just as with CSS Media Queries (which are rendered on the fly)?
I've already tried react-native-responsive module without luck.
Thank you!
If using Hooks. You can refer to this solution: https://stackoverflow.com/a/61838183/5648340
The orientation of apps from portrait to landscape and vice versa is a task that sounds easy but may be tricky in react native when the view has to be changed when orientation changes. In other words, having different views defined for the two orientations can be achieved by considering these two steps.
Import Dimensions from React Native
import { Dimensions } from 'react-native';
To identify the current orientation and render the view accordingly
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
/**
* Returns true of the screen is in landscape mode
*/
const isLandscape = () => {
const dim = Dimensions.get('screen');
return dim.width >= dim.height;
};
To know when orientation changes to change view accordingly
// Event Listener for orientation changes
Dimensions.addEventListener('change', () => {
this.setState({
orientation: Platform.isPortrait() ? 'portrait' : 'landscape'
});
});
Assembling all pieces
import React from 'react';
import {
StyleSheet,
Text,
Dimensions,
View
} from 'react-native';
export default class App extends React.Component {
constructor() {
super();
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
this.state = {
orientation: isPortrait() ? 'portrait' : 'landscape'
};
// Event Listener for orientation changes
Dimensions.addEventListener('change', () => {
this.setState({
orientation: isPortrait() ? 'portrait' : 'landscape'
});
});
}
render() {
if (this.state.orientation === 'portrait') {
return (
//Render View to be displayed in portrait mode
);
}
else {
return (
//Render View to be displayed in landscape mode
);
}
}
}
As the event defined for looking out the orientation change uses this command ‘this.setState()’, this method automatically again calls for ‘render()’ so we don’t have to worry about rendering it again, it’s all taken care of.
Here's #Mridul Tripathi's answer as a reusable hook:
// useOrientation.tsx
import {useEffect, useState} from 'react';
import {Dimensions} from 'react-native';
/**
* Returns true if the screen is in portrait mode
*/
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
/**
* A React Hook which updates when the orientation changes
* #returns whether the user is in 'PORTRAIT' or 'LANDSCAPE'
*/
export function useOrientation(): 'PORTRAIT' | 'LANDSCAPE' {
// State to hold the connection status
const [orientation, setOrientation] = useState<'PORTRAIT' | 'LANDSCAPE'>(
isPortrait() ? 'PORTRAIT' : 'LANDSCAPE',
);
useEffect(() => {
const callback = () => setOrientation(isPortrait() ? 'PORTRAIT' : 'LANDSCAPE');
Dimensions.addEventListener('change', callback);
return () => {
Dimensions.removeEventListener('change', callback);
};
}, []);
return orientation;
}
You can then consume it using:
import {useOrientation} from './useOrientation';
export const MyScreen = () => {
const orientation = useOrientation();
return (
<View style={{color: orientation === 'PORTRAIT' ? 'red' : 'blue'}} />
);
}
You can use the onLayout prop:
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
screen: Dimensions.get('window'),
};
}
getOrientation(){
if (this.state.screen.width > this.state.screen.height) {
return 'LANDSCAPE';
}else {
return 'PORTRAIT';
}
}
getStyle(){
if (this.getOrientation() === 'LANDSCAPE') {
return landscapeStyles;
} else {
return portraitStyles;
}
}
onLayout(){
this.setState({screen: Dimensions.get('window')});
}
render() {
return (
<View style={this.getStyle().container} onLayout = {this.onLayout.bind(this)}>
</View>
);
}
}
}
const portraitStyles = StyleSheet.create({
...
});
const landscapeStyles = StyleSheet.create({
...
});
Finally, I've been able to do so. Don't know the performance issues it can carry, but they should not be a problem since it's only called on resizing or orientation change.
I've made a global controller where I have a function which receives the component (the container, the view) and adds an event listener to it:
const getScreenInfo = () => {
const dim = Dimensions.get('window');
return dim;
}
const bindScreenDimensionsUpdate = (component) => {
Dimensions.addEventListener('change', () => {
try{
component.setState({
orientation: isPortrait() ? 'portrait' : 'landscape',
screenWidth: getScreenInfo().width,
screenHeight: getScreenInfo().height
});
}catch(e){
// Fail silently
}
});
}
With this, I force to rerender the component when there's a change on orientation, or on window resizing.
Then, on every component constructor:
import ScreenMetrics from './globalFunctionContainer';
export default class UserList extends Component {
constructor(props){
super(props);
this.state = {};
ScreenMetrics.bindScreenDimensionsUpdate(this);
}
}
This way, it gets rerendered everytime there's a window resize or an orientation change.
You should note, however, that this must be applied to every component which we want to listen to orientation changes, since if the parent container is updated but the state (or props) of the children do not update, they won't be rerendered, so it can be a performance kill if we have a big children tree listening to it.
Hope it helps someone!
I made a super light component that addresses this issue.
https://www.npmjs.com/package/rn-orientation-view
The component re-renders it's content upon orientation change.
You can, for example, pass landscapeStyles and portraitStyles to display these orientations differently.
Works on iOS and Android.
It's easy to use. Check it out.
React Native also have useWindowDimensions hooks that returns the width and height of your device.
With this, you can check easily if the device is in 'Portrait' or 'Landscape' by comparing the width and height.
See more here
I had the same problem. After the orientation change the layout didn't change.
Then I understood one simple idea - layout should depend on screen width that should be calculated inside render function, i.e.
getScreen = () => {
return Dimensions.get('screen');
}
render () {
return (
<View style={{ width: this.getScreen().width }>
// your code
</View>
);
}
In that case, the width will be calculated at the moment of render.
** I am using this logic for my landscape and portrait Logic.**
** by this if I launch my app in landscape first I am getting the real height of my device. and manage the hight of the header accordingly.**
const [deviceOrientation, setDeviceOrientation] = useState(
Dimensions.get('window').width < Dimensions.get('window').height
? 'portrait'
: 'landscape'
);
const [deviceHeight, setDeviceHeight] = useState(
Dimensions.get('window').width < Dimensions.get('window').height
? Dimensions.get('window').height
: Dimensions.get('window').width
);
useEffect(() => {
const setDeviceHeightAsOrientation = () => {
if (Dimensions.get('window').width < Dimensions.get('window').height) {
setDeviceHeight(Dimensions.get('window').height);
} else {
setDeviceHeight(Dimensions.get('window').width);
}
};
Dimensions.addEventListener('change', setDeviceHeightAsOrientation);
return () => {
//cleanup work
Dimensions.removeEventListener('change', setDeviceHeightAsOrientation);
};
});
useEffect(() => {
const deviceOrientation = () => {
if (Dimensions.get('window').width < Dimensions.get('window').height) {
setDeviceOrientation('portrait');
} else {
setDeviceOrientation('landscape');
}
};
Dimensions.addEventListener('change', deviceOrientation);
return () => {
//cleanup work
Dimensions.removeEventListener('change', deviceOrientation);
};
});
console.log(deviceHeight);
if (deviceOrientation === 'landscape') {
return (
<View style={[styles.header, { height: 60, paddingTop: 10 }]}>
<TitleText>{props.title}</TitleText>
</View>
);
} else {
return (
<View
style={[
styles.header,
{
height: deviceHeight >= 812 ? 90 : 60,
paddingTop: deviceHeight >= 812 ? 36 : 10
}
]}>
<TitleText>{props.title}</TitleText>
</View>
);
}
I have, by far, had the most success with this library: https://github.com/axilis/react-native-responsive-layout
It does what you are asking for and a lot more. Simple Component implementation without hardly any logic like some of the more complex answers above. My project is using Phone, Tablet, and web via RNW - and the implementation is flawless. Additionally when resizing the browser it's truly responsive, and not just on initial rendering - handling phone orientation changes flawlessly.
Example code (Put any components as children of blocks):
<Grid>
<Section> {/* Light blue */}
<Block xsSize="1/1" smSize="1/2" />
<Block xsSize="1/1" smSize="1/2" />
<Block xsSize="1/1" smSize="1/2" />
</Section>
<Section> {/* Dark blue */}
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
<Block size="1/1" smSize="1/2" />
</Section>
</Grid>
To give this:
I have written a HoC solution for my expo SDK36 project, it support orientation change and pass props.orientation based on ScreenOrientation.Orientation value.
import React, { Component } from 'react';
import { ScreenOrientation } from 'expo';
export default function withOrientation(Component) {
class DetectOrientation extends React.Component {
constructor(props) {
super(props);
this.state = {
orientation: '',
};
this.listener = this.listener.bind(this);
}
UNSAFE_componentWillMount() {
this.subscription = ScreenOrientation.addOrientationChangeListener(this.listener);
}
componentWillUnmount() {
ScreenOrientation.removeOrientationChangeListener(this.subscription);
}
listener(changeEvent) {
const { orientationInfo } = changeEvent;
this.setState({
orientation: orientationInfo.orientation.split('_')[0],
});
}
async componentDidMount() {
await this.detectOrientation();
}
async detectOrientation() {
const { orientation } = await ScreenOrientation.getOrientationAsync();
this.setState({
orientation: orientation.split('_')[0],
});
}
render() {
return (
<Component
{...this.props}
{...this.state}
onLayout={this.detectOrientation}
/>
);
}
}
return (props) => <DetectOrientation {...props} />;
}
To achieve a more performant integration, I used the following as a superclass for each of my react-navigation screens:
export default class BaseScreen extends Component {
constructor(props) {
super(props)
const { height, width } = Dimensions.get('screen')
// use this to avoid setState errors on unmount
this._isMounted = false
this.state = {
screen: {
orientation: width < height,
height: height,
width: width
}
}
}
componentDidMount() {
this._isMounted = true
Dimensions.addEventListener('change', () => this.updateScreen())
}
componentWillUnmount() {
this._isMounted = false
Dimensions.removeEventListener('change', () => this.updateScreen())
}
updateScreen = () => {
const { height, width } = Dimensions.get('screen')
if (this._isMounted) {
this.setState({
screen: {
orientation: width < height,
width: width, height: height
}
})
}
}
Set any root components to extend from this component, and then pass the screen state to your leaf/dumb components from the inheriting root components.
Additionally, to keep from adding to the performance overhead, change the style object instead of adding more components to the mix:
const TextObject = ({ title }) => (
<View style={[styles.main, screen.orientation ? styles.column : styles.row]}>
<Text style={[styles.text, screen.width > 600 ? {fontSize: 14} : null ]}>{title}</Text>
</View>
)
const styles = StyleSheet.create({
column: {
flexDirection: 'column'
},
row: {
flexDirection: 'row'
},
main: {
justifyContent: 'flex-start'
},
text: {
fontSize: 10
}
}
I hope this helps anyone in the future, and you'll find it to be quite optimal in terms of overhead.
I'm using styled-components, and this is how I re-render the UI on orientation change.
import React, { useState } from 'react';
import { View } from 'react-native';
import { ThemeProvider } from 'styled-components';
import appTheme from 'constants/appTheme';
const App = () => {
// Re-Layout on orientation change
const [theme, setTheme] = useState(appTheme.getTheme());
const onLayout = () => {
setTheme(appTheme.getTheme());
}
return (
<ThemeProvider theme={theme}>
<View onLayout={onLayout}/>
{/* Components */}
</ThemeProvider>
);
}
export default App;
Even if you're not using styled-components, you can create a state and update it on onLayout to re-render the UI.
This is my solution:
const CheckOrient = () => {
console.log('screenHeight:' + Dimensions.get('screen').height + ', screenWidth: ' + Dimensions.get('screen').width);
}
return ( <
View onLayout = {
() => CheckOrient()
} >
............
<
/View>
Note for the case with a pure component. #mridul-tripathi answer works correctly, but if a pure component is used, then probably only parent/top-level component reacting to orientation change is not enough. You will also need to update a pure component separately on orientation change.
All you need is:
import { useWindowDimensions } from 'react-native';
export default function useOrientation() {
const window = useWindowDimensions();
return window.height >= window.width ? 'portrait' : 'landscape';
}
You need useWindowDimensions
This hook re-render component when dimension change and apply styles but Dimensions object can't re-render component and change style, it just work in first render
import { useWindowDimensions } from 'react-native';
then destructure it
const { height, width } = useWindowDimensions();
and final you can do like this
import React from "react";
import { View, StyleSheet, useWindowDimensions } from "react-native";
const App = () => {
const { height, width } = useWindowDimensions();
const isPortrait = height > width;
return (
<View style={isPortrait ? styles.portrait : styles.landscape}>
{/* something */}
</View>
);
};
const styles = StyleSheet.create({
portrait: {},
landscape: {},
});
export default App;
also you can use scale property
const { scale } = useWindowDimensions();
read this document
https://reactnative.dev/docs/usewindowdimensions