React Material UI - Export multiple higher order components - reactjs

I'm stuck on exporting material-ui styles with redux connector. Here is my code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Drawer from 'material-ui/Drawer';
import { withStyles } from 'material-ui/styles';
import PropTypes from 'prop-types';
const mapStateToProps = state => ({});
const reduxConnector = connect(mapStateToProps, null);
const styles = theme => {
console.log(theme);
return ({
paper: {
top: '80px',
boxShadow: theme.shadows[9]
},
});
};
class Cart extends Component {
render() {
const { classes } = this.props;
return (
<Drawer
open
docked
anchor="right"
classes={{ paper: classes.paper }}
>
<p style={{ width: 250 }}>cart</p>
</Drawer>
);
}
}
export default withStyles(styles, {name: 'Cart'})(Cart);
export default reduxConnector(Cart); // I want to add this
I've tried:
export default reduxConnector(withStyles(styles))(Cart); // return Uncaught TypeError: Cannot call a class as a function
export default withStyles(styles, {name: 'Cart'})(reduxConnector(Cart)); // return Uncaught Error: Could not find "store" in either the context or props of "Connect(Cart)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Cart)".
Any solution?

Just try this
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(App));
Where App is your component.
It works fine for me.

Take a look at how it's being handled in the material-ui docs site, specifically in the AppFrame component:
export default compose(
withStyles(styles, {
name: 'AppFrame',
}),
withWidth(),
connect(),
)(AppFrame);
They're using recompose to do this.
So in your case, it would be:
import React, { Component } from 'react';
import compose from 'recompose/compose';
import { connect } from 'react-redux';
import Drawer from 'material-ui/Drawer';
import { withStyles } from 'material-ui/styles';
import PropTypes from 'prop-types';
const styles = theme => {
console.log(theme);
return {
paper: {
top: '80px',
boxShadow: theme.shadows[9],
},
};
};
const Cart = ({ classes }) =>
<Drawer open docked anchor="right" classes={{ paper: classes.paper }}>
<p style={{ width: 250 }}>cart</p>
</Drawer>;
const mapStateToProps = state => ({});
export default compose(
withStyles(styles, { name: 'Cart' }),
connect(mapStateToProps, null)
)(Cart);

Without recompose or compose:
Cart = withStyles(styles, {name: 'Cart'})(Cart);
export default reduxConnector(Cart);

install npm install recompose or yarn add recompose
and on your export section
export default compose(
withStyles(styles, {
name: 'App',
}),
connect(),
)(AppFrame);
and I forgot to export my store.

This works perfect for me
export default connect(mapStateToProps)((withStyles(styles)(ComponentNameToExport)));

You may use this below. As both withStyles and connect were higher order components
export default withStyles(styles, {name: 'Cart'})(connect(mapStateToProps, mapDispatchToProps), Cart);

Complete Component
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import ExpansionPanel from "#material-ui/core/ExpansionPanel";
import ExpansionPanelSummary from "#material-ui/core/ExpansionPanelSummary";
import ExpansionPanelDetails from "#material-ui/core/ExpansionPanelDetails";
import Typography from "#material-ui/core/Typography";
import ExpandMoreIcon from "#material-ui/icons/ExpandMore";
import { withStyles } from "#material-ui/core/styles";
import { connect } from "react-redux";
import { fetchPosts } from "../../store/actions/postActions";
import PropTypes from "prop-types";
const useStyles = theme => ({
root: {
marginLeft: 250,
marginRight: 10
},
heading: {
fontSize: "1rem",
fontWeight: theme.typography.fontWeightRegular
}
});
class SimpleExpansionPanel extends React.Component {
UNSAFE_componentWillMount() {
this.props.fetchPosts();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.newPost) {
this.props.postsa.unshift(nextProps.newPost);
}
}
render() {
const { classes } = this.props;
const postItem = this.props.postsa.map(post => (
<ExpansionPanel key={post.id}>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header">
<Typography className={classes.heading}>{post.title}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>{post.body}</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
));
return <div className={classes.root}>{postItem}</div>;
}
}
SimpleExpansionPanel.propTypes = {
fetchPosts: PropTypes.func.isRequired,
postsa: PropTypes.array.isRequired,
newPost: PropTypes.object
};
const mapStateToProps = state => ({
postsa: state.postss.items,
newPost: state.postss.item
});
export default connect(
mapStateToProps,
{ fetchPosts }
)(withStyles(useStyles)(SimpleExpansionPanel));

Related

Material UI: "styled" child component not applying css rules

I'm having an issue where I'm using the styled function to style a custom React component but the styles are not being applied. In the example below, I would expect the Child component to have the color: red style, but it doesn't. The sibling component, however, is styled correctly.
import "./styles.css";
import { Child } from "./Child";
import { Typography } from "#mui/material";
import { styled } from "#mui/material/styles";
const StyledChild = styled(Child)(() => ({
color: "red"
}));
const StyledSibling = styled(Typography)(() => ({
color: "blue"
}));
export default function App() {
return (
<>
<StyledChild />
<StyledSibling>Sibling</StyledSibling>
</>
);
}
import { Typography } from "#mui/material";
import { FunctionComponent } from "react";
export const Child: FunctionComponent = () => {
return <Typography>Child</Typography>;
};
CodeSandbox
styled causes a className prop to be passed to the wrapped component, but Child isn't passing the className prop along to Typography.
Here's an example of how to fix Child.tsx:
import { Typography } from "#mui/material";
import { FunctionComponent } from "react";
export const Child: FunctionComponent<{ className?: string }> = ({
className
}) => {
return <Typography className={className}>Child</Typography>;
};

material ui styling not applying

For some reason my material ui styles are not applying to my html element? Any idea why? I have no other styles applied to this page
import React, { Component } from 'react';
import LoginForm from '../components/form/loginForm';
import { makeStyles } from '#material-ui/core';
const classes = makeStyles( (theme) => ({
root: {
paddingTop: theme.spacing(8),
backgroundColor: "white"
},
}) )
class Login extends Component {
render() {
return(
<div className = {classes.root}>
<LoginForm/>
</div>
);
}
}
export default Login;
In your case, if you want to style your class component, you should use withStyles. Try this:
import React, { Component } from 'react';
import LoginForm from '../components/form/loginForm';
import { withStyles } from '#material-ui/core/styles';
const useStyles = (theme) => ({
root: {
paddingTop: theme.spacing(8),
backgroundColor: "white"
},
})
class Login extends Component {
render() {
const { classes } = this.props
return(
<div className = {classes.root}>
<LoginForm/>
</div>
);
}
}
export default withStyles(useStyles)(Login);
makeStyles returns a react hook to use in the component. Hooks also only work in functional components, so you'll need to convert Login to a functional component.
import React, { Component } from 'react';
import LoginForm from '../components/form/loginForm';
import { makeStyles } from '#material-ui/core';
const useStyles = makeStyles(theme => ({
root: {
paddingTop: theme.spacing(8),
backgroundColor: "lightblue"
}
}));
const Login = props => {
const classes = useStyles();
return(
<div className={classes.root}>
<LoginForm/>
</div>
);
}
export default Login;

Context & Provider not working - render is not a function

Been trying many things but for I cannot get this working for the life of me. Been looking at various Context tutorials but no. Could you help me out?
App.js
import React from 'react'
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import AccountScreen from './src/screens/AccountScreen'
import SigninScreen from './src/screens/SigninScreen'
import SignupScreen from './src/screens/SignupScreen'
import TrackCreateScreen from './src/screens/TrackCreateScreen'
import TrackDetailScreen from './src/screens/TrackDetailScreen'
import TrackListScreen from './src/screens/TrackListScreen'
import UserProvider from './src/context/appContext'
const switchNavigator = createSwitchNavigator({
loginFlow: createStackNavigator({
Signup: SignupScreen,
Signin: SigninScreen
}),
mainFlow: createBottomTabNavigator({
trackListFlow: createStackNavigator({
TrackList: TrackListScreen,
TrackDetail: TrackDetailScreen
}),
TrackCreate: TrackCreateScreen,
Account: AccountScreen
})
})
const App = createAppContainer(switchNavigator)
export default () => {
return (
<UserProvider value={'hey'}>
<App />
</UserContext.Provider>
)
}
appContext.js
import React, { createContext } from 'react'
const UserContext = React.createContext()
export const UserProvider = UserContext.Provider
export const UserConsumer = UserContext.Consumer
export default UserContext
SignupScreen.js
import React, { useState, useContext, Component } from 'react'
import { View, StyleSheet, Button } from 'react-native'
import { Text, Input } from 'react-native-elements'
import Spacer from '../components/Spacer'
import { signUpUser } from '../functions/functions.js'
import { UserContext } from '../context/appContext.js'
const SignupScreen = ({ navigation, value }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
return (
<View style={styles.container}>
<Spacer>
<Text h3>Sign up for goodgrowth</Text>
</Spacer>
<Input
label="Email"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
autoCorrect={false}
/>
<Spacer />
<Input
secureTextEntry
label="Password"
value={password}
onChangeText={setPassword}
autoCapitalize="none"
autoCorrect={false}
/>
<Spacer>
<Button
title="Sign up"
onPress={() => signUpUser(email, password)}
/>
<Button
title="Sign in"
onPress={() => navigation.navigate('Signin')}
/>
</Spacer>
</View>
)
}
SignupScreen.navigationOptions = () => {
return {
header: null
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
marginBottom: 250
}
})
export default SignupScreen
TypeError: render is not a function. (In 'render(newValue)', 'render' is an instance of Object).
Why is this not working?
You have exported UserProvider as a named export but you are importing as default in App.js which gives you UserContext and not UserProvider
Also your syntax of UserProvider is incorrect.
Use it like
import { UserProvider } from './src/context/appContext' // named import
...
export default () => {
return (
<UserProvider value={'hey'}>
<App />
</UserProvider>
)
}
Using the curly braces seemed to have done the trick! Thanks Shubham!
I also had to change the following in SignupScreen, so the other way around, from yes-curly-braces to no-curly-braces:"
import { UserContext } from '../context/appContext.js'
TO
import UserContext from '../context/appContext.js'
Because UserContext was the default export there. But it's still a React object, no? Why would it be 'undefined' if I left it in curly braces?

React & Material-UI: Theme undefined in createStyles() with TypeScript

I'm learning React with TypeScript and using the Material UI framework for the frontend. I try to get the media queries working, but I got an error:
Uncaught TypeError: Cannot read property 'up' of undefined
at styles (webpack-internal:///./app/components/navigation/Toolbar/index.tsx:59)
This is the corresponding code:
const styles = ({breakpoints}: Theme) => createStyles( {
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: 'none',
[breakpoints.up('md')]: {
display: 'flex'
}
},
sectionMobile: {
display: 'flex'
},
})
The styles are passed to the component with:
export default withStyles(styles)(Toolbar)
I read that it is not required to create a custom theme as the default one will be passed automatically to the functions. However, the breakpoints property of theme is undefined which cause a blank page.
Thanks for your help
Edit
Here is the code of the component which will still produce the problem without any other components.
import * as React from 'react'
import {
Theme
} from '#material-ui/core'
import {
createStyles,
WithStyles,
withStyles
} from '#material-ui/styles'
// import Drawer from '../Drawer'
const styles = ({breakpoints}: Theme) => createStyles( {
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: 'none',
[breakpoints.up('md')]: {
display: 'flex'
}
},
sectionMobile: {
display: 'flex'
},
})
namespace Toolbar {
interface Props {
}
export interface State {
isOpen : boolean
mobileMoreAnchorEl? : EventTarget & HTMLElement
}
export type AllProps = Props & WithStyles<typeof styles>
}
class Toolbar extends React.Component<Toolbar.AllProps, Toolbar.State> {
constructor(props: Toolbar.AllProps, context?: any) {
super(props, context);
this.state = { isOpen: false, mobileMoreAnchorEl: undefined}
}
render() {
const { classes } = this.props
// const { isOpen } = this.state
return(
<React.Fragment>
<div className={classes.sectionDesktop}>
Hello
</div>
<div className={classes.sectionMobile}>
World
</div>
</React.Fragment>
)
}
}
export default withStyles(styles)(Toolbar)
The main.tsx (a.k.a index.js) looks like this:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createBrowserHistory } from 'history';
import { configureStore } from 'app/store';
import { Router } from 'react-router';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import { App } from './app';
// prepare store
const history = createBrowserHistory()
const store = configureStore()
const theme = createMuiTheme()
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<MuiThemeProvider theme={theme} >
<App />
</MuiThemeProvider>
</Router>
</Provider>,
document.getElementById('root')
);
So, adding MuiThemeProvider does not help.
UPDATE
At the time when this answer was first written, #material-ui/styles was unstable. It is not anymore (as of v4), but it is still generally best to import from #material-ui/core/styles since the default theme will not be available when importing from #material-ui/styles.
You can read here that #material-ui/styles is unstable (alpha version).
You'll notice in my CodeSandbox that I am using:
import { withStyles, createStyles } from "#material-ui/core/styles";
instead of importing these from #material-ui/styles. When I use the same import as you, I am able to reproduce your problem.
UPDATE for v5
In v5, usage of makeStyles and withStyles is deprecated and they were removed from #material-ui/core and are only accessible via #material-ui/styles. In v5, makeStyles and withStyles do not have access to the theme unless you provide access via the ThemeProvider (and for that to work you should be using the latest v5 versions of both #material-ui/core and #material-ui/styles). Below is a working v5 example.
import React from "react";
import ReactDOM from "react-dom";
import { createTheme, ThemeProvider } from "#material-ui/core/styles";
import { withStyles } from "#material-ui/styles";
const styles = ({ breakpoints }) => ({
grow: {
flexGrow: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20
},
sectionDesktop: {
display: "none",
[breakpoints.up("md")]: {
display: "flex"
}
},
sectionMobile: {
display: "flex"
}
});
const MyToolbar = (props) => {
return (
<>
<div className={props.classes.sectionDesktop}>Section Desktop</div>
{props.children}
<div className={props.classes.sectionMobile}>Section Mobile</div>
</>
);
};
const theme = createTheme();
const StyledToolbar = withStyles(styles)(MyToolbar);
function App() {
return (
<ThemeProvider theme={theme}>
<StyledToolbar>
<div>Hello</div>
<div>CodeSandbox</div>
</StyledToolbar>
</ThemeProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

implement BackButton in react-admin

I need to implement a <BackButton /> in react-admin for example when I open show page for a resource I need able to back to the list page.
Can you guide me to implement this?
I'm not familiar with react-admin routing mechanism.
Now I'm using this component in my edit form actions props:
const MyActions = ({ basePath, data, resource }) => (
<CardActions>
<ShowButton basePath={basePath} record={data} />
<CloneButton basePath={basePath} record={data} />
{/* Need <BackButton /> here */}
</CardActions>
);
export const BookEdit = (props) => (
<Edit actions={<MyActions />} {...props}>
<SimpleForm>
...
</SimpleForm>
</Edit>
);
You can use react-router-redux's goBack() function to achieve this.
For example, your button component will look something like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Button from '#material-ui/core/Button';
import { goBack } from 'react-router-redux';
class BackButton extends Component {
handleClick = () => {
this.props.goBack();
};
render() {
return <Button variant="contained" color="primary" onClick={this.handleClick}>Go Back</Button>;
}
}
export default connect(null, {
goBack,
})(BackButton);
Now use that button component in your CardActions.
You can get help from an example which uses react-router-redux's push() function in a similar way from the official docs.
Create a back button. This one will pass props and children (text) and uses react-router directly, which I think makes more sense and keeps your code simple.
// BackButton.js
import React from 'react'
import Button from '#material-ui/core/Button'
import { withRouter } from 'react-router'
const BackButton = ({ history: { goBack }, children, ...props }) => (
<Button {...props} onClick={goBack}>
{children}
</Button>
)
export default withRouter(BackButton)
Example usage:
import { Toolbar, SaveButton } from 'react-admin'
import BackButton from './BackButton'
const SomeToolbar = props => (
<Toolbar {...props}>
<SaveButton />
<BackButton
variant='outlined'
color='secondary'
style={{ marginLeft: '1rem' }}
>
Cancel
</BackButton>
</Toolbar>
)
The complete code is below.
//BackButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import compose from 'recompose/compose';
import { withStyles, createStyles } from '#material-ui/core/styles';
import { translate } from 'ra-core';
import Button from '#material-ui/core/Button';
import ArrowBack from '#material-ui/icons/ArrowBack';
import classnames from 'classnames';
import { fade } from '#material-ui/core/styles/colorManipulator';
const styles = theme =>
createStyles({
deleteButton: {
color: theme.palette.error.main,
'&:hover': {
backgroundColor: fade(theme.palette.error.main, 0.12),
// Reset on mouse devices
'#media (hover: none)': {
backgroundColor: 'transparent',
},
},
},
});
const sanitizeRestProps = ({
basePath,
className,
classes,
label,
invalid,
variant,
translate,
handleSubmit,
handleSubmitWithRedirect,
submitOnEnter,
record,
redirect,
resource,
locale,
...rest
}) => rest;
class BackButton extends Component {
static contextTypes = {
router: () => true, // replace with PropTypes.object if you use them
}
static propTypes = {
label: PropTypes.string,
refreshView: PropTypes.func.isRequired,
icon: PropTypes.element,
};
static defaultProps = {
label: 'ra.action.back',
icon: <ArrowBack />,
};
render() {
const {
className,
classes = {},
invalid,
label = 'ra.action.back',
pristine,
redirect,
saving,
submitOnEnter,
translate,
icon,
onClick,
...rest
} = this.props;
return (
<Button
onClick={this.context.router.history.goBack}
label={label}
className={classnames(
'ra-back-button',
classes.backButton,
className
)}
key="button"
{...sanitizeRestProps(rest)}>
{icon} {label && translate(label, { _: label })}
</Button>
)
}
}
const enhance = compose(
withStyles(styles),
translate
);
export default enhance(BackButton);
//Toolbar.js
import React from 'react';
import {
Toolbar,
SaveButton,
DeleteButton,
} from 'react-admin';
import { withStyles } from '#material-ui/core';
import BackButton from './BackButton'
const toolbarStyles = {
toolbar: {
display: 'flex',
justifyContent: 'space-between',
},
};
export const CustomEditToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<DeleteButton/>
<BackButton/>
</Toolbar>
));
export const CustomCreateToolbar = withStyles(toolbarStyles)(props => (
<Toolbar {...props}>
<SaveButton/>
<BackButton/>
</Toolbar>
));

Resources