Material-UI withStyles not adding classes to props - reactjs

I'm trying to implement some styling using Material-UI's withStyles method, however I'm not able to get classes as a prop. Any suggestions as to what I'm missing? I've included the relevant code below, but note that there is an <App> component in this file that I'm leaving out for brevity.
import React from 'react'
import ReactDOM from "react-dom";
import {Paper, Typography} from '#material-ui/core'
import {withStyles} from '#material-ui/core/styles'
import NavBar from "./navBar";
class Recipe extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('Recipe Did Mount')
}
render() {
const {recipeData, classes} = this.props;
return (
<Paper>
<Typography className={classes.recipeName}>{recipeData.name}</Typography>
<Typography className={classes.recipeIngredients}>{recipeData.ingredients}</Typography>
<Typography className={classes.recipeInstructions}>{recipeData.instructions}</Typography>
</Paper>
)
}
}
const styles = {
root: {
fontSize: "1.0rem",
margin: "0px"
},
recipeName: {
fontSize: "1.0rem",
margin: "0px"
},
recipeIngredients: {
fontSize: "1.0rem",
margin: "0px" },
recipeInstructions: {
fontSize: "1.0rem",
margin: "0px" }
};
withStyles(styles)(Recipe);
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<App/>,
document.body.appendChild(document.createElement('div')),
)
});

Since you aren't setting withStyles(styles)(Recipe); into a variable, I suspect you must be using Recipe directly within App.
withStyles doesn't change Recipe. withStyles creates a new component that wraps Recipe and passes the classes prop to it. In order to see the classes prop, you need to use the newly-created component with something like the following:
const StyledRecipe = withStyles(styles)(Recipe);
const App = ()=> {
return <StyledRecipe/>;
}

Assuming App is defined in a separate file (for others who may come looking for this question), change the
`withStyles(styles)(Recipe);`
To
export default withStyles(styles)(Recipe);
As Ryan already explained ' withStyles is the higher order component that creates and returns a new component'

Related

Styling Class Components with an alternative to makeStyles

I am seeing examples of using the makeStyles hook so you can style your functional component in Material Design. But I am using a class component and so can't use hooks. The code I see being used for functional components is as follows:
const useStyles = makeStyles((theme) => ({
margin: {
margin: theme.spacing(1),
},
}));
And then for styling the elements in the return() section, they do something like this:
className={classes.margin}
How do I do the same type of thing but for a class component?
for class component you can use withStyles wrapper.
import React, { Component } from "react";
import { withStyles } from "#material-ui/core/styles";
class App extends Component {
render() {
const { classes } = this.props;
return <div className={classes.styledLine}>Styling using withStyles</div>;
}
}
const useStyles = (theme) => ({
styledLine: {
color: "red"
}
});
export default withStyles(useStyles)(App);
Working demo:-

Passing custom props to each styled component through Provider

I would like to pass a custom prop (exactly: theme name as string) to each passed styled component through Provider, so it was available throughout the css definition.
ThemeProvider almost does it, but it expects object, not the string. I do not want to pass whole object with theme settings, just the name of my theme.
I do not want to use special theme prop or similar, because then I would have to it manually every single time I create new styled component. Provider seems like the best option if only it cooperated with string.
Is there any possibility to pass a string through Provider to Consumer builded in styled components?
EDIT:
[PARTIAL SOLUTION]
I found what I was looking for when I realized styled-components exports their inner context. That was it. Having access to pure react context gives you original Provider, without any 'only objects' restriction ('only objects' is a styled-components custom provider restriction).
Now I can push to each styled component exactly what I want and if I want.
import styled, { ThemeContext } from 'styled-components';
const StyledComponent = styled.div`
color: ${props => props.theme == 'dark' ? 'white' : 'black'};
`;
const Component = props => {
const theme = 'dark';
return (
<ThemeContext.Provider value={theme}>
<NextLevelComponent>
<StyledComponent />
</NextLevelComponent>
</ThemeContext.Provider>
);
};
Hope I have this correct, from what I've been able to glean. I haven't tried this out but it seems it might work for you. This is lifted directly from the reactjs.org docs regarding context. It passed the string name of the theme down.
const ThemeContext = React.createContext('green');
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="blue">
<SomeComponent />
</ThemeContext.Provider>
);
}
}
function SomeComponent(props) {
return (
<div>
<OtherComponent />
</div>
);
}
class OtherComponent extends React.Component {
static contextType = ThemeContext;
render() {
return <ThirdComponent theme={this.context} />
}
}
I hope this helps you understand the idea behind ThemeContext from styled-components. I've passed string "blue" to ThemeContext just to show, that it should not be object and you can use just string.
import React, { useContext } from "react";
import ReactDOM from "react-dom";
import styled, { ThemeContext } from "styled-components";
// Define styled button
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
color: ${props => props.theme};
border: 2px solid ${props => props.theme};
`;
// Define the name of the theme / color (string)
const themeName = "blue";
const ThemedButton = () => {
// Get the name from context
const themeName = useContext(ThemeContext);
return <Button theme={themeName}>Themed color: {themeName}</Button>;
};
function App() {
return (
<div className="App">
<ThemeContext.Provider value={themeName}>
<ThemedButton />
</ThemeContext.Provider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Demo: https://codesandbox.io/s/styled-components-example-with-themecontext-cso55

Invalid hook call. Hooks can only be called inside of the body of a function component when apply style to class base component with material-ui

I am just started to learn reactjs using material-ui but getting this error when apply style to my component. My code:
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
}));
class NavMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false
};
}
render() {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="Menu"
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
className={classes.title}
>
News
</Typography>
<Button color="inherit">Login</Button>
</Toolbar>
</AppBar>
</div>
);
}
}
export default NavMenu;
and this is Error:
material-ui makeStyles function only works inside function components, as it uses the new React Hooks APIs inside.
You have two options:
Convert your class component to a functional component.
Use a Higher Order Component as in material-ui docs
I personally recommend the first approach, as this is becoming the new standard in React development.
This tutorial may help you get started with functional components
and check the docs for React Hooks
Use withStyles:
App.js:
import {withStyles} from '#material-ui/core/styles'
// ...
const styles = theme => ({
paper: {
padding: theme.spacing(2),
// ...
},
// ...
})
class App extends React.Component {
render() {
const {classes} = this.props
// ...
}
}
export default withStyles(styles)(App)
Root.js:
import React, {Component} from 'react'
import App from './App'
import {ThemeProvider} from '#material-ui/styles'
import theme from '../theme'
export default class Root extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<App/>
</ThemeProvider>
)
}
}
theme.js:
import {createMuiTheme} from '#material-ui/core/styles'
const theme = createMuiTheme({
palette: {
primary: ...
secondary: ...
},
// ...
}
export default theme
See Theming - Material-UI.
See Higher-order component API.
if you have created a functional component and still run into this issue... the next thing to look for are the dependency versions.
I tried a new stackblitz project to test a material-ui component and got this error. My dependencies were:
react 16.12
react-dom 16.12
#material-ui/core 4.9.14
So I had to change to latest react version using react#latest and react-dom#latest which got me to the following:
react 16.13.1
react-dom 16.13.1
#material-ui/core 4.9.14
Sharing here so that it can help other people who run into this... thanks to this post for the hint

How to pass Styled-Component theme variables to Components?

Within my React+StyledComponent app, I have a theme file like so:
theme.js:
const colors = {
blacks: [
'#14161B',
'#2E2E34',
'#3E3E43',
],
};
const theme = {
colors,
};
export default theme;
Currently, I can easily use these colors to style my components like so:
const MyStyledContainer = styled.div`
background-color: ${(props) => props.theme.colors.blacks[1]};
`;
The problem is, how do I pass blacks[1] to a Component as the prop of the color to use like so:
<Text color="black[1]">Hello</Text>
Where Text.js is:
const StyledSpan = styled.span`
color: ${(props) => props.theme.colors[props.color]};
`;
const Text = ({
color,
}) => {
return (
<StyledSpan
color={color}
>
{text}
</StyledSpan>
);
};
Text.propTypes = {
color: PropTypes.string,
};
export default Text;
Currently the above is silently failing and rending the following in the DOM:
<span class="sc-brqgn" color="blacks[1]">Hello</span>
Any ideas on how I can get this to work? Thank you
EDIT: Updated to use styled-components withTheme HOC
New answer
You could wrap the component rendering <Text> in the higher order component (HOC) withTheme provided by styled-components. This enables you to use the theme given to the <ThemeProvider> directly in the React component.
Example (based on the styled-components docs):
import React from 'react'
import { withTheme } from 'styled-components'
import Text from './Text.js'
class MyComponent extends React.Component {
render() {
<Text color={this.props.theme.colors.blacks[1]} />;
}
}
export default withTheme(MyComponent)
Then you could do
const MyStyledContainer = styled.div`
background-color: ${(props) => props.color};
`;
Old answer
You could import the theme where you render and pass <Text color={theme.blacks[1]} />.
import theme from './theme.js'
...
<Text color={theme.colors.blacks[1]} />
Then you could do
const MyStyledContainer = styled.div`
background-color: ${(props) => props.color};
`;
You can use defaultProps
import PropTypes from 'prop-types'
MyStyledContainer.defaultProps = { theme }
App.js
App gets theme and passes color to Text
import React, { Component } from 'react'
import styled from 'styled-components'
const Text = styled.div`
color: ${props => props.color || 'inherit'}
`
class App extends Component {
render() {
const { theme } = this.props
return (
<Text color={theme.colors.black[1]} />
)
}
}
export default App
Root.js
Root component passes theme to entire application.
import React, { Component } from 'react'
import { ThemeProvider } from 'styled-components'
import theme from './theme'
import App from './App'
class Root extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
)
}
}
export default Root
If you're using functional components in React and v4.x and higher styled-components, you need to leverage useContext and styled-components' ThemeContext. Together, these allow you to use your theme settings inside of components that aren't styled-components.
import { useContext } from 'react'
import { ThemeContext } from 'styled-components'
export default function MyComponent() {
// place ThemeContext into a context that is scoped to just this component
const themeProps = useContext(ThemeContext)
return(
<>
{/* Example here is a wrapper component that needs sizing params */}
{/* We access the context and all of our theme props are attached to it */}
<Wrapper maxWidth={ themeProps.maxWidth }>
</Wrapper>
</>
)
}
Further reading in the styled-components docs: https://styled-components.com/docs/advanced#via-usecontext-react-hook

React createElement type is invalid

I am having trouble trying to publish a node module, material-ui-next-datepicker
It is working locally but not when installed as a node module
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import {MuiThemeProvider, createMuiTheme, withStyles, StyledComponentProps, Theme} from 'material-ui/styles'
import DateFormatInput from 'material-ui-next-datepicker'
const theme = createMuiTheme()
const styles = (theme:Theme):Record<string, React.CSSProperties> => ({
container: {
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
})
#(withStyles as any)(styles)
class DemoPage extends React.Component<DemoPageProps, DemoPageState> {
constructor(props) {
super(props)
this.state = {
date: undefined
}
}
onChange = (date:Date) => {
console.log(date)
this.setState({date})
}
render() {
const {classes} = this.props
const {date} = this.state
return (
<div className={classes.container}>
<DateFormatInput name='date-input' value={date} onChange={this.onChange} label='Date'/>
</div>
)
}
}
interface DemoPageProps extends React.Props<{}>, StyledComponentProps {
}
interface DemoPageState {
date: Date
}
ReactDOM.render(
<MuiThemeProvider theme={theme}>
<DemoPage/>
</MuiThemeProvider>
, document.getElementById('root'))
Here's the error in the browser
When I do print out DateFormatInput, it looks fine...definitely not undefined
I am not too familiar with dependencies and how to differentiate between peer, ambient or bundled. So, I do need some help in making this node module work
Finally able to solve this issue simply by adding .npmignore
I removed all my demo files like:
demo/
src/
index.html
tsconfig.json
webpack.config.js
Then it starts working fine by importing...

Resources