Comment styliser des composants à l'aide de makeStyles tout en conservant des méthodes de cycle de vie dans Material UI?

117

J'obtiens l'erreur ci-dessous chaque fois que j'essaye d'utiliser makeStyles()avec un composant avec des méthodes de cycle de vie:

Appel de hook non valide. Les hooks ne peuvent être appelés qu'à l'intérieur du corps d'un composant de fonction. Cela peut se produire pour l'une des raisons suivantes:

  1. Vous pouvez avoir des versions incompatibles de React et du moteur de rendu (comme React DOM)
  2. Vous enfreignez peut-être les règles des Hooks
  3. Vous pouvez avoir plus d'une copie de React dans la même application

Voici un petit exemple de code qui produit cette erreur. D'autres exemples affectent également des classes aux éléments enfants. Je ne trouve rien dans la documentation de MUI qui montre d'autres façons d'utiliser makeStyleset j'ai la possibilité d'utiliser des méthodes de cycle de vie.

    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
la source

Réponses:

170

Salut au lieu d'utiliser l'API hook, vous devriez utiliser l'API de composant d'ordre supérieur comme mentionné ici

Je modifierai l'exemple de la documentation en fonction de vos besoins en composant de classe

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/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 HigherOrderComponent extends React.Component {

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

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

export default withStyles(styles)(HigherOrderComponent);
Vikas Kumar
la source
4
J'ai tourné en rond avec ce bug et l' invalid hook callerreur - Merci de m'avoir amené dans la bonne direction !!
Kitson
1
@ Jax-p voir ma solution
Matt Weber
4
@VikasKumar Avec cette approche, comment puis-je utiliser le thème d'application dans mes styles? Fe soumettre: {margin: appTheme.spacing (3, 0, 2),},
Sergey Aldoukhov
1
Merci. Mais un problème! Vous n'avez pas utilisé themedans votre stylescorps (@SergeyAldoukhov l'a déjà dit). Quand je l'utilise, j'obtiens cette erreur: "Impossible de lire la propriété 'X' de undefined" et undefinedc'est themeexactement! J'ai essayé withStyles(styles(myDefinedMuiTheme))(...)et cela a fonctionné correctement.
Mir-Ismaili le
1
@Kitson, vous avez probablement utilisé makeStyles() ( styles = makeStyles(theme => ({...})) . De plus, si vous voulez un style dépendant du thème, consultez mon commentaire précédent.
Mir-Ismaili le
41

J'ai utilisé withStylesau lieu demakeStyle

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
la source
18

Ce que nous avons fini par faire, c'est cesser d'utiliser les composants de classe et créer des composants fonctionnels, en utilisantuseEffect() l' API Hooks pour les méthodes de cycle de vie . Cela vous permet de continuer à utiliser les makeStyles()méthodes du cycle de vie sans ajouter la complication de la création de composants d'ordre supérieur . Ce qui est beaucoup plus simple.

Exemple:

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
la source
2
Pour les personnes utilisant la mise à jour React 16.8 Hooks ou une version supérieure, je pense que le passage à une fonction est une solution idéale. Dans 16.8, les fonctions peuvent accéder aux hooks d'état et de cycle de vie.
Tim
5
Je ne sais pas pourquoi cela a entraîné des votes négatifs. React a clairement indiqué que les classes sont remplacées par des composants fonctionnels avec Hooks. reactjs.org/docs/…
Matt Weber
3
Je n'ai pas voté contre, mais il est difficile de définir l'état initial de manière paresseuse en utilisant xhr tout en utilisant un composant basé sur une fonction. Avec le composant de classe, je peux définir l'état initial sur ce que je veux, puis utiliser ajax puis setState une fois que la réponse arrive. Je n'ai absolument aucune idée de comment le faire correctement avec une fonction.
mlt
1
Vous utiliseriez useEffect. Dans le cas ci-dessus, vous définissez l'état initial de userName sur une chaîne vide, puis après un appel d'API effectué, assurez- useEffectvous de l'utiliser setUserName(response). J'ajouterai un exemple ci-dessus et un lien vers un article avec plus d'informations sur l'utilisation de useEffect pour les méthodes de cycle de vie. dev.to/prototyp/…
Matt Weber
3
Cela est en train d'être voté car la programmation fonctionnelle aspire les applications réelles qui ont besoin d'une architecture. Cela renforce la tendance déjà proliférante des programmeurs js à créer de gros morceaux de code spaghetti qui sont vraiment, vraiment difficiles à lire / suivre et impossibles à diviser en composants raisonnables. Si réagir pousse de cette façon, ils font une grosse erreur, et je ne les suivrai pas là-bas.
RickyA
2

useStyles est un hook React destiné à être utilisé dans les composants fonctionnels et ne peut pas être utilisé dans les composants de classe.

De React:

Les hooks vous permettent d'utiliser l'état et d'autres fonctionnalités React sans écrire de classe.

Vous devez également appeler useStyleshook dans votre fonction comme;

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

Si vous souhaitez utiliser des hooks, voici votre bref composant de classe transformé en composant fonctionnel;

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;

🏓 sur ↓ CodeSandBox ↓

Modifier les hooks React

Hasan Sefa Ozalp
la source
0

Une autre solution peut être utilisée pour les composants de classe - il suffit de remplacer les propriétés de thème MUI par défaut avec MuiThemeProvider. Cela donnera plus de flexibilité par rapport aux autres méthodes - vous pouvez utiliser plus d'un MuiThemeProvider dans votre composant parent.

étapes simples:

  1. importer MuiThemeProvider dans votre composant de classe
  2. importez createMuiTheme dans votre composant de classe
  3. créer un nouveau thème
  4. enveloppe le composant MUI cible que vous souhaitez styliser avec MuiThemeProvider et votre thème personnalisé

veuillez consulter ce document pour plus de détails: 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;

Viktorma
la source
-1

Au lieu de convertir la classe en fonction, une étape facile serait de créer une fonction pour inclure le jsx pour le composant qui utilise les 'classes', dans votre cas <container></container>, puis appelez cette fonction dans le retour de la classe render () comme étiquette. De cette façon, vous déplacez le crochet vers une fonction de la classe. Cela a parfaitement fonctionné pour moi. Dans mon cas, c'est une fonction <table>que j'ai déplacée vers une fonction - TableStmt à l'extérieur et j'ai appelé cette fonction à l'intérieur du rendu comme<TableStmt/>

Jayesh
la source