React.JS | Theming con JSS

En ocasiones, al desarrollar aplicaciones web es necesario realizar un diseño que acepte el cambio de Tema para adaptarlo a las necesidades del cliente de forma dinámica y que pueda personalizar su experiencia. Si estamos usando React.JS junto con JSS para aplicar los estilos, podemos hacerlo de forma manual añadiendo el código necesario o, usar la característica de Theming de JSS.

¿Qué es JSS?

JSS es una herramienta que permite combinar JavaScript con CSS para poder escribir los estilos de forma declarativa, reutilizable, flexible,… Además es independiente del framework que usemos, aunque, en nuestro caso, lo aplicaremos a React.

Gracias a esta herramienta podremos usar estilos dinámicos, dependientes del valor de determinadas variables de nuestro código JavaScript y, este fundamento es la base sobre la que podremos aplicar temas de una forma sencilla

ThemeProvider

Vamos a entrar un poco en faena, así que empezaremos por ThemeProvider. Ya que estamos haciendo uso de React, es fácil pensar que una solución adecuada es usar un componente y como elementos hijos, aquellos que queramos que varíen su comportamiento dependiendo del tema seleccionado.

ThemeProvider es el componente que envolverá aquellos a los que deseamos cambiar su comportamiento según el tema seleccionado, de forma que se propagará por todos ellos para poder ser utilizado según convenga.

      <ThemeProvider theme={currentTheme}>
        Children...
      </ThemeProvider>

El tema lo definiremos como un objeto que contiene los estilos que vamos a utilizar, organizado o agrupado a nuestra conveniencia.

const lightTheme = {
  value: "light",
  colors: {
    background: "#ffffff",
    foreground: "#0c0c0c",
    border: 'cyan'
  }
}
const darkTheme = {
  value: "dark",
  colors: {
    background: "#000000",
    foreground: "#c0c0c0",
    border: 'cyan'
  }
}

Obtener el tema usado

Para poder obtener el tema desde un componente, tenemos dos opciones diferentes «withTheme» y «useTheme».

Es importante tener en cuenta que los componente sólo podrán acceder al tema si alguno de los componentes de orden superior es un ThemeProvider

withTheme

El uso de withTheme se realiza dentro del propio componente a la hora de exportarlo y esto hará que el tema se añada como propiedad necesaria al invocarlo desde un componente superior.

Nuestro componente:

const ThemedBlockStyles = createUseStyles((theme: any) => ({
  themedBlock: {
    padding: "20px",
    margin: "20px",
    textAlign: "center",
    border: '1px solid ' + theme.colors.border,
    borderRadius: '20px',
    background: theme.colors.background,
    color: theme.colors.foreground
  }
}));

const ThemedBlockWithTheme = (props: any) => {
  const styles = ThemedBlockStyles(props.theme);

  return (
    <>
    <div className={styles.themedBlock}>
      Hello themed world!!
    </div>
    </> 
  )
};
export default withTheme(ThemedBlockWithTheme);

El componente de orden superior

<ThemedBlockWithTheme theme={currentTheme} />

Como se puede observar, al invocar el componente, es necesario pasarle como parámetro/atributo el tema y, posteriormente, en el propio componente se podrá hacer uso de ese tema desde las propiedades mediante «props.theme» aprovechando para pasárselo a los estilos creados con JSS.

useTheme

useTheme tiene un uso un poco más «clásico» y permite, desde cualquier componente, acceder al tema sin necesidad de que se le tenga que pasar como atributo a la hora de invocarlo.

const ThemedBlockStyles = createUseStyles((theme: any) => ({
  themedBlock: {
    padding: "20px",
    margin: "20px",
    textAlign: "center",
    border: '1px solid ' + theme.colors.border,
    borderRadius: '20px',
    background: theme.colors.background,
    color: theme.colors.foreground
  }
}));

const ThemedBlockUseTheme = (props: any) => {
  const theme = useTheme();
  const styles = ThemedBlockStyles({...props, theme});

  return (
    <>
    <div className={styles.themedBlock}>
      Hello themed world!!
    </div>
    </> 
  )
};
export default ThemedBlockUseTheme;

Y el componente de orden superior ya no tendría que pasarle como atributo el tema a la hora de instanciarlo.

        <ThemedBlockUseTheme />

Ejemplo

Si queréis ver el ejemplo, podéis ver el código en mi repo de GitHub y usarlo a vuestro gusto.

Mejoras del ejemplo

Tema del sistema

Para mejorar el ejemplo, podéis mirar cómo obtener el tema del sistema usando window.matchMedia(«(prefers-color-scheme: dark)»); y así mostrar vuestros componentes con el tema adecuado a la selección del usuario en su sistema. Podéis indagar en este artículo.

Extender la interfaz DefaultTheme para definir el tema y tener Intellisense

Como podéis comprobar, en el código de ejemplo he indicado «any» como tipo del parámetro theme dentro de los estilos JSS, de forma que pueda recibir cualquier objeto con la agrupación deseada. Lo malo de esto es que no podemos garantizar que lo que escribamos exista, así que, una mejor forma, es extender la interfaz DefaultTheme agregando las propiedades que queramos tener dentro de nuestro tema y así no sólo disponer de intellisense sino que podemos garantizar que las propiedades que vayamos a intentar usar existan en nuestro tema.

Enjoy coding!