168

I get the below error whenever I try to use makeStyles() with a component with lifecycle methods:

Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app

Below is a small example of code that produces this error. Other examples assign classes to child items as well. I can't find anything in MUI's documentation that shows other ways to use makeStyles and have the ability to use lifecycle methods.

    import React, { Component } from 'react';
    import { Redirect } from 'react-router-dom';

    import { Container, makeStyles } from '@material-ui/core';

    import LogoButtonCard from '../molecules/Cards/LogoButtonCard';

    const useStyles = makeStyles(theme => ({
      root: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      },
    }));

    const classes = useStyles();

    class Welcome extends Component {
      render() {
        if (this.props.auth.isAuthenticated()) {
          return <Redirect to="/" />;
        }
        return (
          <Container maxWidth={false} className={classes.root}>
            <LogoButtonCard
              buttonText="Enter"
              headerText="Welcome to PlatformX"
              buttonAction={this.props.auth.login}
            />
          </Container>
        );
      }
    }

    export default Welcome;
Matt Weber
  • 2,808
  • 2
  • 14
  • 30

8 Answers8

225

Hi instead of using hook API, you should use Higher-order component API as mentioned here

I'll modify the example in the documentation to suit your need for class component

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';

const styles = theme => ({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    border: 0,
    borderRadius: 3,
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
    color: 'white',
    height: 48,
    padding: '0 30px',
  },
});

class HigherOrderComponentUsageExample extends React.Component {
  
  render(){
    const { classes } = this.props;
    return (
      <Button className={classes.root}>This component is passed to an HOC</Button>
      );
  }
}

HigherOrderComponentUsageExample.propTypes = {
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(HigherOrderComponentUsageExample);
Vikas Kumar
  • 2,978
  • 3
  • 14
  • 24
  • Great! Yea the examples don't show this method. I am curious though, how would you use your "HighOrderComponent" in another .js file. You can use "withStyles(styles)(HigherOrderComponent)" to instantiate one, but how to you reference the styles in HigherOrderComponent's .js file? And do you just pass props the usual way? – ThePartyTurtle Jul 10 '19 at 19:53
  • @ThePartyTurtle yes the passing of props as usual. using it will be in another js file will be the same as before. just import HigherOrderComponent from 'HigherOrderComponent.js' . – Vikas Kumar Jul 11 '19 at 02:22
  • Ohh gotcha. Turns out I was confused because I hadn't seen the "export default withStyles(styles)(HigherOrderComponent);" syntax yet and I was confused if that was a function I needed to call... It's been a while since I last developed a web app, and I last used TypeScript haha. Thanks for the clarification. – ThePartyTurtle Jul 11 '19 at 16:11
  • 5
    I've been running round in circles with this bug and the `invalid hook call` error - Thanks for getting me in the right direction!! – Kitson Jul 21 '19 at 15:38
  • This is fine but can I get somehow `props` into it? I need to make conditional styles. – Jax-p Oct 15 '19 at 15:33
  • 1
    @Jax-p see my solution – Matt Weber Oct 26 '19 at 14:49
  • 5
    @VikasKumar With this approach, how can I use app theme in my styles? F.e. submit: { margin: appTheme.spacing(3, 0, 2), }, – Sergey Aldoukhov Nov 08 '19 at 02:00
  • Would you you add typings for TypeScript to know what the `classes` object contains? – XCS Nov 10 '19 at 12:58
  • 1
    Thanks. But a problem! You didn't use `theme` in your `styles` body (@SergeyAldoukhov has said this, already). When I use it, I get this error: *"Cannot read property 'X' of undefined"* and `undefined` is `theme` exactly! I tried **`withStyles(styles(myDefinedMuiTheme))(...)`** and it worked correctly. – Mir-Ismaili Jan 14 '20 at 21:46
  • 1
    @Kitson, **Probably you have used `makeStyles()`** *(`styles = makeStyles(theme => ({...})`)*. *Also, if you want theme-dependent style, see my previous comment.* – Mir-Ismaili Jan 14 '20 at 21:55
  • This should be the answer. – Hasan Sefa Ozalp Feb 04 '20 at 22:01
  • 1
    Hi future Googlers: please use `import { withStyles } from '@material-ui/core/styles'` and not as stated in the answer, due to https://github.com/mui-org/material-ui/issues/15976#issuecomment-499774398 – Vaiden Jul 11 '21 at 14:04
95

I used withStyles instead of makeStyle

EX :

import { withStyles } from '@material-ui/core/styles';
import React, {Component} from "react";

const useStyles = theme => ({
        root: {
           flexGrow: 1,
         },
  });

class App extends Component {
       render() {
                const { classes } = this.props;
                return(
                    <div className={classes.root}>
                       Test
                </div>
                )
          }
} 

export default withStyles(useStyles)(App)
Hamed
  • 1,166
  • 8
  • 10
29

What we ended up doing is stopped using the class components and created Functional Components, using useEffect() from the Hooks API for lifecycle methods. This allows you to still use makeStyles() with Lifecycle Methods without adding the complication of making Higher-Order Components. Which is much simpler.

Example:

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Redirect } from 'react-router-dom';

import { Container, makeStyles } from '@material-ui/core';

import LogoButtonCard from '../molecules/Cards/LogoButtonCard';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    margin: theme.spacing(1)
  },
  highlight: {
    backgroundColor: 'red',
  }
}));

// Highlight is a bool
const Welcome = ({highlight}) => { 
  const [userName, setUserName] = useState('');
  const [isAuthenticated, setIsAuthenticated] = useState(true);
  const classes = useStyles();

  useEffect(() => {
    axios.get('example.com/api/username/12')
         .then(res => setUserName(res.userName));
  }, []);

  if (!isAuthenticated()) {
    return <Redirect to="/" />;
  }
  return (
    <Container maxWidth={false} className={highlight ? classes.highlight : classes.root}>
      <LogoButtonCard
        buttonText="Enter"
        headerText={isAuthenticated && `Welcome, ${userName}`}
        buttonAction={login}
      />
   </Container>
   );
  }
}

export default Welcome;
Matt Weber
  • 2,808
  • 2
  • 14
  • 30
  • 3
    For people using React 16.8 Hooks update or greater, I think switching to a function is an ideal solution. In 16.8 functions can access state and lifecycle hooks. – Tim Oct 25 '19 at 23:04
  • 7
    I am baffled why this has gotten downvotes. React has made it pretty clear classes are being replaced by functional components with Hooks. https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both – Matt Weber Dec 05 '19 at 19:13
  • 4
    I did not downvote, but it is a pain to set initial state the lazy way using xhr while using function based component. With class component I can set initial state to whatever I want then use ajax then setState once response arrives. I totally have no clue how to do it nicely with a function. – mlt Dec 10 '19 at 00:00
  • 1
    You would use `useEffect`. In the case above you are setting the initial state of userName to an empty string, then after an API call done insure of `useEffect` you would use `setUserName(response)`. I'll add an example above and a link to a artical with more info on useEffect's use for lifecycle methods. https://dev.to/prototyp/react-useeffect-explained-with-lifecycle-methods-296n – Matt Weber Dec 10 '19 at 18:56
  • 6
    This is getting down voted because functional programming sucks in actual applications that need architecture. It enhances the already proliferate tendency of js programmers to make big turds of spaghetti code that are really, really hard to read/follow and impossible to be split out in reasonable components. If react is pushing this way they are making a big mistake, and I will not follow them there. – RickyA Dec 12 '19 at 09:46
3

useStyles is a React hook which are meant to be used in functional components and can not be used in class components.

From React:

Hooks let you use state and other React features without writing a class.

Also you should call useStyles hook inside your function like;

function Welcome() {
  const classes = useStyles();
...

If you want to use hooks, here is your brief class component changed into functional component;

import React from "react";
import { Container, makeStyles } from "@material-ui/core";

const useStyles = makeStyles({
  root: {
    background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)",
    border: 0,
    borderRadius: 3,
    boxShadow: "0 3px 5px 2px rgba(255, 105, 135, .3)",
    color: "white",
    height: 48,
    padding: "0 30px"
  }
});

function Welcome() {
  const classes = useStyles();
  return (
    <Container className={classes.root}>
      <h1>Welcome</h1>
    </Container>
  );
}

export default Welcome;

on ↓ CodeSandBox ↓

Edit React hooks

Hasan Sefa Ozalp
  • 6,353
  • 5
  • 34
  • 45
0

Instead of converting the class to a function, an easy step would be to create a function to include the jsx for the component which uses the 'classes', in your case the <container></container> and then call this function inside the return of the class render() as a tag. This way you are moving out the hook to a function from the class. It worked perfectly for me. In my case it was a <table> which i moved to a function- TableStmt outside and called this function inside the render as <TableStmt/>

jayesh
  • 761
  • 6
  • 4
0

Another one solution can be used for class components - just override default MUI Theme properties with MuiThemeProvider. This will give more flexibility in comparison with other methods - you can use more than one MuiThemeProvider inside your parent component.

simple steps:

  1. import MuiThemeProvider to your class component
  2. import createMuiTheme to your class component
  3. create new theme
  4. wrap target MUI component you want to style with MuiThemeProvider and your custom theme

please, check this doc for more details: https://material-ui.com/customization/theming/

import React from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';

import { MuiThemeProvider } from '@material-ui/core/styles';
import { createMuiTheme } from '@material-ui/core/styles';

const InputTheme = createMuiTheme({
    overrides: {
        root: {
            background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
            border: 0,
            borderRadius: 3,
            boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
            color: 'white',
            height: 48,
            padding: '0 30px',
        },
    }
});

class HigherOrderComponent extends React.Component {

    render(){
        const { classes } = this.props;
        return (
            <MuiThemeProvider theme={InputTheme}>
                <Button className={classes.root}>Higher-order component</Button>
            </MuiThemeProvider>
        );
    }
}

HigherOrderComponent.propTypes = {
    classes: PropTypes.object.isRequired,
};

export default HigherOrderComponent;
0

Further to the answer provided by @vikas-kumar, it's also possible to make use of the props that are being set on the component being styled, e.g.

const styles = theme => ({
  root: {
    background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
    border: 0,
    borderRadius: 3,
    boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
    color: 'white',
    height: props => props.height,
    padding: '0 30px',
  },
});

So the height for the style applied can be governed by

<HigherOrderComponentUsageExample height={48}/>

Further details on dynamic styling are available here: https://material-ui.com/styles/basics/#adapting-the-higher-order-component-api

Shiraz
  • 2,310
  • 24
  • 26
-1

You are calling useStyles hook outside of a function.Thats why

Ummer Zaman
  • 336
  • 3
  • 4