¿Cómo diseñar componentes usando makeStyles y aún tener métodos de ciclo de vida en Material UI?

117

Recibo el siguiente error cada vez que intento usarlo makeStyles()con un componente con métodos de ciclo de vida:

Llamada de gancho no válida. Los ganchos solo se pueden llamar dentro del cuerpo de un componente de función. Esto podría suceder por una de las siguientes razones:

  1. Es posible que tenga versiones no coincidentes de React y el renderizador (como React DOM)
  2. Puede que estés infringiendo las reglas de los ganchos
  3. Es posible que tenga más de una copia de React en la misma aplicación

A continuación se muestra un pequeño ejemplo de código que produce este error. Otros ejemplos también asignan clases a elementos secundarios. No puedo encontrar nada en la documentación de MUI que muestre otras formas de usar makeStylesy tenga la capacidad de usar métodos de ciclo de vida.

    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
fuente

Respuestas:

170

Hola, en lugar de usar la API de enlace, debe usar la API de componentes de orden superior como se menciona aquí

Modificaré el ejemplo en la documentación para adaptarlo a su necesidad de componente de clase.

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
fuente
4
He estado dando vueltas en círculos con este error y el invalid hook callerror: ¡¡Gracias por llevarme en la dirección correcta !!
Kitson
1
@ Jax-p ve mi solución
Matt Weber
4
@VikasKumar Con este enfoque, ¿cómo puedo usar el tema de la aplicación en mis estilos? Fe enviar: {margin: appTheme.spacing (3, 0, 2),},
Sergey Aldoukhov
1
Gracias. ¡Pero un problema! No usaste themeen tu stylescuerpo (@SergeyAldoukhov ya ha dicho esto). Cuando lo uso, aparece este error: "No se puede leer la propiedad 'X' de indefinido" y undefinedes themeexactamente! Lo intenté withStyles(styles(myDefinedMuiTheme))(...)y funcionó correctamente.
Mir-Ismaili
1
@Kitson, probablemente hayas usado makeStyles() ( styles = makeStyles(theme => ({...})) . Además, si desea un estilo dependiente del tema, consulte mi comentario anterior.
Mir-Ismaili
41

Usé en withStyleslugar 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
fuente
18

Lo que terminamos haciendo fue dejar de usar los componentes de la clase y crear componentes funcionales, usandouseEffect() la API Hooks para los métodos del ciclo de vida . Esto le permite seguir utilizando los makeStyles()métodos de ciclo de vida sin agregar la complicación de crear componentes de orden superior . Que es mucho más sencillo.

Ejemplo:

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
fuente
2
Para las personas que usan la actualización React 16.8 Hooks o superior, creo que cambiar a una función es una solución ideal. En 16.8, las funciones pueden acceder a los ganchos de estado y ciclo de vida.
Tim
5
Estoy desconcertado por qué esto ha recibido votos negativos. React ha dejado bastante claro que las clases están siendo reemplazadas por componentes funcionales con Hooks. reactjs.org/docs/…
Matt Weber
3
No voté en contra, pero es una molestia establecer el estado inicial de forma perezosa usando xhr mientras se usa el componente basado en funciones. Con el componente de clase, puedo establecer el estado inicial en lo que quiera, luego usar ajax y luego establecer el estado una vez que llegue la respuesta. No tengo ni idea de cómo hacerlo bien con una función.
mlt
1
Usarías useEffect. En el caso anterior, está configurando el estado inicial de userName en una cadena vacía, luego, después de realizar una llamada a la API, asegúrese de useEffectusar setUserName(response). Agregaré un ejemplo arriba y un enlace a un artículo con más información sobre el uso de useEffect para los métodos del ciclo de vida. dev.to/prototyp/…
Matt Weber
3
Esto está siendo rechazado porque la programación funcional apesta en aplicaciones reales que necesitan arquitectura. Mejora la tendencia ya proliferada de los programadores de js de hacer grandes cantidades de código espagueti que son realmente muy difíciles de leer / seguir e imposibles de dividir en componentes razonables. Si reaccionar está empujando de esta manera, están cometiendo un gran error y no los seguiré allí.
RickyA
2

useStyles es un gancho de React que está destinado a ser utilizado en componentes funcionales y no se puede utilizar en componentes de clase.

De React:

Los Hooks te permiten usar el estado y otras características de React sin escribir una clase.

También debes llamar a useStyleshook dentro de tu función como;

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

Si desea utilizar ganchos, aquí está su breve componente de clase convertido en componente funcional;

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;

🏓 en ↓ CodeSandBox ↓

Editar ganchos de React

Hasan Sefa Ozalp
fuente
0

Se puede usar otra solución para los componentes de la clase: simplemente anule las propiedades predeterminadas del tema MUI con MuiThemeProvider. Esto le dará más flexibilidad en comparación con otros métodos; puede usar más de un MuiThemeProvider dentro de su componente principal.

pasos sencillos:

  1. importar MuiThemeProvider a su componente de clase
  2. importar createMuiTheme a su componente de clase
  3. crear un tema nuevo
  4. envuelva el componente MUI de destino que desea diseñar con MuiThemeProvider y su tema personalizado

Por favor, consulte este documento para obtener más detalles: 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
fuente
-1

En lugar de convertir la clase en una función, un paso fácil sería crear una función para incluir el jsx para el componente que usa las 'clases', en su caso, <container></container>y luego llamar a esta función dentro del retorno de la clase render () como una etiqueta. De esta manera, está moviendo el enlace a una función de la clase. Funcionó perfectamente para mí. En mi caso, fue un <table>que moví a una función: TableStmt afuera y llamé a esta función dentro del render como<TableStmt/>

Jayesh
fuente