Passing styles from parent to mui component in react - reactjs

Hi I have the following React component that positions its children with styles.
const styles = () => ({
bathButt : {
top :278,
left : 336
},
})
class AudioZones extends Component {
render() {
const { classes } = this.props;
return (
<IconButton className={classes.bathButt} >
<Speaker/>
</IconButton>
);
}
}
export default withStyles(styles) (AudioZones);
I have created a child component "AudioZone"
render()
return (
);
}
which i substitute into the parent
render() {
const { classes } = this.props;
return (
<AudioZone/> );
}
However I have run into trouble on how I pass down the "bathButt" style so that the position of the button is set in the parent but read and rendered by the child.
Any help appreciated

For withStyles you can use Higher-Order Components (HOC) to pass styles from parent to child
const styles = () => ({
bathButt: {
top: 20,
left: 30,
backgroundColor: "blue"
}
});
const withMyStyles = WrappedComponent => {
const WithStyles = ({ classes }) => {
return (
<div>
<WrappedComponent classes={classes} />
</div>
);
};
return withStyles(styles)(WithStyles);
};
and use it in your child component
class AudioZones extends Component {
render() {
const { classes } = this.props;
return (
<IconButton className={classes.bathButt}>
<h1>Speaker Component</h1>
</IconButton>
);
}
}
export default withMyStyles(AudioZones);
but insted of withStyles you can use makeStyles,i think its easer
const useStyles = makeStyles({
bathButt: { top: 20, left: 50, color: "red" } // a style rule
});
function App(props) {
return <AudioZones useStyles={useStyles} />;
}
child component
function AudioZones(props) {
const classes = useStyles();
return (
<div>
<IconButton className={classes.bathButt}>
<h1>Speaker Component</h1>
</IconButton>
</div>
);
}
Working Codesandbox for withStyles and makeStyles

Related

How to get parent ref in functional component

I've trying to use addEventListener in the Functional component in order to attach the onclick event to its parent.
So when its parent(the red box) is clicked the console.log("Test") is should prints.
At first I should get a ref of it to access the its parent.
So I tried:
https://codesandbox.io/s/red-framework-vv9j7
import React, { useRef, useEffect } from "react";
interface ContextMenuProps {
isVisible: boolean;
}
const ContextMenu: React.FC<ContextMenuProps> = props => {
const thisComponent = useRef<any>(this);
useEffect(() => {
if (thisComponent && thisComponent.current) {
thisComponent.current.addEventListener("click", test);
}
}, []);
const test = () => {
console.log("Test");
};
return <></>;
};
export default ContextMenu;
////////////////////////////////////
function App() {
return (
<div
className="App"
style={{ width: "200px", height: "200px", backgroundColor: "red" }}
>
<Test />
</div>
);
}
But, thisComponent is undefined.

How can I convert popover MATERIAL-UI functional component to class based component?

I'm trying to convert this functional component to class based component. I have tried for several hours but could not find where to place these const variables in component. If someone could write it out in class based component it will highly appreciated.
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
function handleClick(event) {
setAnchorEl(anchorEl ? null : event.currentTarget);
}
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
return (
<div>
<Button aria-describedby={id} variant="contained" onClick={handleClick}>
Toggle Popper
</Button>
<Popper id={id} open={open} anchorEl={anchorEl} transition>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>The content of the Popper.</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
export default SimplePopper;
import React, { Component } from "react";
import { createMuiTheme } from "#material-ui/core/styles";
import Typography from "#material-ui/core/Typography";
import Button from "#material-ui/core/Button";
import Fade from "#material-ui/core/Fade";
import Paper from "#material-ui/core/Paper";
import Popper from "#material-ui/core/Popper";
import { withStyles } from "#material-ui/styles";
const theme = createMuiTheme({
spacing: 4
});
const styles = {
typography: {
padding: theme.spacing(2)
}
};
class SimplePopper extends Component {
constructor(props) {
super(props);
this.state = { anchorEl: null, open: false };
}
flipOpen = () => this.setState({ ...this.state, open: !this.state.open });
handleClick = event => {
this.state.ancherEl
? this.setState({ anchorEl: null })
: this.setState({ anchorEl: event.currentTarget });
this.flipOpen();
};
render() {
const open = this.state.anchorEl === null ? false : true;
console.log(this.state.anchorEl);
console.log(this.state.open);
const id = this.state.open ? "simple-popper" : null;
const { classes } = this.props;
return (
<div>
<Button
aria-describedby={id}
variant="contained"
onClick={event => this.handleClick(event)}
>
Toggle Popper
</Button>
<Popper
id={id}
open={this.state.open}
anchorEl={this.state.anchorEl}
transition
>
{({ TransitionProps }) => (
<Fade {...TransitionProps} timeout={350}>
<Paper>
<Typography className={classes.typography}>
The content of the Popper.
</Typography>
</Paper>
</Fade>
)}
</Popper>
</div>
);
}
}
export default withStyles(styles)(SimplePopper);
First thing one need to understand is, how class based and functional components work. Also, when and where you use it.
In short, I can say functional components are Used for presenting static data. And class based are Used for dynamic source of data.
Here are few links for your reference.
Class based component vs Functional components what is the difference ( Reactjs ) and React functional components vs classical components
To answer your specific question.
import React, { Component } from 'react';
import { withStyles, makeStyles } from '#material-ui/styles';
const useStyles = makeStyles(theme => ({
typography: {
padding: theme.spacing(2),
},
}));
class SimplePopper extends Component {
constructor(props){
super(props)
this.state = { anchorEl: null, setAnchorEl: null }; <--- Here see the state creation
this.handleClick= this.handleClick.bind(this);
}
handleClick(event) {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
setAnchorEl(anchorEl ? null : event.currentTarget);
}
render() {
const { anchorEl, setAnchorEl } = this.state; <--- Here accessing the state
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : null;
const { classes } = this.props;
return (
<div>
............Rest of the JSX............
</div>
);
}
}
export default withStyles(useStyles)(SimplePopper);
Note that here I've used withStyles to wrap the style to your component. So, that the styles will be available as classNames.
Explore the difference and convert the rest
This is more enough to begin with.

Change a prop on a styled component and update appearance

I'm very new to styled components (and I'm not great with React in general) and I can't quite get my head around them. I've created a basic example which is an abstraction of what I want to achieve. When I click on the box, I want the property on to be changed to true and for the colour of <Box> to be updated to green as per the background-color rule.
How do I achieve this? Especially in the instance where there could be an indeterminate number of boxes?
Component
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const Box = styled.a`
display: block;
height: 100px;
width: 100px;
background-color: ${props => props.on ? 'green' : 'red' };
`;
Box.propTypes = {
on: PropTypes.bool,
onClick: PropTypes.func,
}
Box.defaultProps = {
on: false,
onClick: () => {},
}
export default Box;
Implementation
<Box on={ false } onClick={ }></Box>
// App.js
import React from "react";
import ReactDOM from "react-dom";
import Test from "./Test";
class App extends React.Component {
state = {
on: false
};
handleChange = () => {
this.setState(prevState => ({ on: !prevState.on }));
};
render() {
return (
<div className="App">
<Test on={this.state.on} onClick={this.handleChange}>
Hey
</Test>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// Test.js
import PropTypes from "prop-types";
import styled from "styled-components";
const Box = styled.a`
display: block;
height: 100px;
width: 100px;
background-color: ${props => (props.on ? "green" : "red")};
`;
Box.propTypes = {
on: PropTypes.bool,
onClick: PropTypes.func
};
Box.defaultProps = {
on: false,
onClick: () => {}
};
export default Box;
You would handle the state in its parent component. For example you could do something like this:
class Page extends Component {
state = { on: false }
handleClick = () => {
this.setState(prevState => ({ on: !prevState.on }));
}
render() {
return <Box on={this.state.on} onClick={this.handleClick} />
}
}
Or even simpler using React hooks:
const Page = () => {
const [on, setOn] = useState(false);
return <Box on={on} onClick={() => setOn(on => !on)} />;
};
Here's an example of what you could do if you wanted 10 boxes
(note: creating the onClick handler in the render method like I did could cause performance issues if you have a very large number of boxes)
class Page extends Component {
state = { boxes: Array(10).fill(false) }
handleClick = (index) => {
this.setState(prevState => ({
boxes: [
...prevState.boxes.slice(0, index),
!prevState.boxes[index],
...prevState.boxes.slice(index)
]
}));
}
render() {
return (
<React.Fragment>
{this.state.boxes.map((on, index) =>
<Box on={on} onClick={() => this.handleClick(index)} />
)}
</React.Fragment>
);
}
}

react-transition-group CSSTransition, conditional with null

Trying to get a simple fade in/out transition working.
I've tried moving the <CSSTransition> around into different areas to no avail. I'm using this successfully in another component that's mapping children but can't see why it wouldn't work in this case since I'm rendering it together with the child component, if the child gets returned at all.
Child component
const Error = (props) => {
return (
<CSSTransition timeout={400} classNames={errorTransition}>
<span> {props.errorString} </span>
</CSSTransition>
)
}
Parent component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { CSSTransition } from 'react-transition-group';
import type { InfoState } from './state';
import { closeError } from './actions';
const mapStateToProps = (state: {info: InfoState}) => ({
info: state.info.data.info,
infoError: state.info.infoError,
});
const mapDispatchToProps = dispatch => ({
closeError: () => dispatch(closeError()),
});
class Parent extends Component<Props, State> {
state = { info: this.props.info };
handleChange = (e) => {
this.setState({ info: e.target.value });
this.props.closeError();
}
render() {
if (this.props.info === null) {
return (
<div className="info-wrapper">
<input
type="text"
value={this.state.info ? this.state.info : '' }
onChange={this.handleChange}
/>
</div>
<div className="info-error">
{ this.props.infoError !== ''
? <Error
key={this.state.info}
errorString={this.props.infoError}
/>
: null
}
</div>
)
}
return ( <div> things </div> )
}
}
CSS
.errorTransition-enter {
opacity: 0.01;
}
.errorTransition-enter-active {
opacity: 1;
transition: all 400ms ease-out;
}
.errorTransition-exit {
opacity: 1;
}
.errorTransition-exit-active {
opacity: 0.01;
transition: all 400ms ease-out;
}
I was having a similar issue with conditionally removing an element wrapped with <CSSTransition>. To solve the problem I wrapped the <CSSTransition> element with a <TransitionGroup> element and used its childFactory prop. The childFactory prop can be used like so:
<TransitionGroup
childFactory={child => React.cloneElement(child)}
>
<CSSTransition timeout={400} classNames={errorTransition}>
<span> {props.errorString} </span>
</CSSTransition>
</TransitionGroup>

Passing props to a Function as a Child component

This works, but I need to separate out the cellRenderer component.
// Grid.js
import React, { Component } from "react";
class Grid extends Component {
render() {
const index = 3;
return (
<div style={{ height: "5em", width: "6em", border: "1px solid black" }}>
{this.props.text}
{this.props.children({ index, cellText: "no." })}
</div>
);
}
}
export default Grid;
And App.js. If I click on "no.3", it correctly logs "x: 6"
import React, { Component } from "react";
import Grid from "./Grid";
class App extends Component {
constructor(props) {
super(props);
this.state = {
x: 5
};
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
);
};
cellRenderer = ({ index, cellText }) => {
return <div onClick={() => this.handleIncrement()}>{cellText + index}</div>;
};
render() {
return (
<div className="App">
<Grid text={"Hello "}>{this.cellRenderer}</Grid>
</div>
);
}
}
export default App;
Now, if I have to separate out cellRenderer component as below, how can I pass the handleIncrement function to it ?
import React, { Component } from "react";
import Grid from "./Grid";
const cellRenderer = ({ index, cellText }) => {
return <div>{cellText + index}</div>;
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
x: 5
};
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
);
};
render() {
return (
<div className="App">
<Grid text={"Hello "}>{cellRenderer}</Grid>
</div>
);
}
}
Edit:
This works:
// pass handleIncrement to Grid
<Grid text={"Hello "} handleIncrement={this.handleIncrement} >{cellRenderer}</Grid>
// And within Grid, pass it to cellRenderer
{this.props.children({ index, cellText: "no.", handleIncrement: this.props.handleIncrement })}
// Update cellRenderer to this
const cellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>;
};
But the problem is that Grid is a component from the library react-window, and I cannot override the library code. Any other way possible ?
The fact that you have separated our cellRenderer as a functional component, you could render it as a component and pass on the props
const CellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>;
};
...
return (
<div className="App">
<Grid text={"Hello "}>{({index, cellText}) => <CellRenderer handleIncrement={this.handleIncrement} index={index} cellText={cellText}/>}</Grid>
</div>
);
This will work:
import React, { Component } from 'react'
import Grid from './components/Grid'
const CellRenderer = ({ index, cellText, handleIncrement }) => {
return <div onClick={handleIncrement}>{cellText + index}</div>
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
x: 5
}
}
handleIncrement = () => {
this.setState(
state => ({ x: state.x + 1 }),
() => console.log(`x: ${this.state.x}`)
)
}
render() {
return (
<div className="App">
<Grid text={'Hello '}>
{props => (
<CellRenderer {...props} handleIncrement={this.handleIncrement} />
)}
</Grid>
</div>
)
}
}
export default App

Resources