¿Cómo usar la declaración de cambio dentro de un componente React?

111

Tengo un componente React, y dentro del rendermétodo del componente tengo algo como esto:

render() {
    return (
        <div>
            <div>
                // removed for brevity
            </div>

           { switch(...) {} }

            <div>
                // removed for brevity
            </div>
        </div>
    );
}

Ahora el caso es que tengo dos divelementos, uno en la parte superior y otro en la parte inferior, que están fijos. En el medio, quiero tener una declaración de cambio y, de acuerdo con un valor en mi estado, quiero representar un componente diferente. Básicamente, quiero que los dos divelementos estén fijos siempre, y solo en el medio para representar un componente diferente cada vez. Estoy usando esto para implementar un procedimiento de pago de varios pasos). Sin embargo, como es el código actualmente, no funciona, ya que me da un error que dice que switches inesperado. ¿Alguna idea de cómo lograr lo que quiero?

errores tipográficos
fuente
1
Bueno, no es necesario tener toda esa lógica en la returndeclaración o incluso el rendermétodo para el caso. ¿Podría definir cada uno <div>como una constante y luego usar el switch antes de su returnpara determinar cuál <div>debe renderizarse?
Jared Goguen
@JaredGoguen Pero luego, necesitaría repetir el diven la parte superior e inferior, varias veces para cada caso de switch. O simplemente te entendí mal, tú ...
errores tipográficos
no, puede crear un código let middleDiv = ...y luego incluirlo {middleDiv}en su JSX de retorno entre los dos <div>s que ha codificado allí.
Jared Goguen

Respuestas:

201

Prueba esto, que también es mucho más limpio: saca ese interruptor del render en una función y simplemente llámalo pasando los parámetros que quieras. Por ejemplo:

renderSwitch(param) {
  switch(param) {
    case 'foo':
      return 'bar';
    default:
      return 'foo';
  }
}

render() {
  return (
    <div>
      <div>
          // removed for brevity
      </div>
      {this.renderSwitch(param)}
      <div>
          // removed for brevity
      </div>
    </div>
  );
}

Kelvin De Moya
fuente
94

A diferencia de otras respuestas, preferiría incluir el "interruptor" en la función de renderizado. Deja más claro qué componentes se pueden renderizar en esa posición. Puede implementar una expresión similar a un interruptor utilizando un objeto javascript antiguo simple:

render () {
  return (
    <div>
      <div>
        {/* removed for brevity */}
      </div>
      {
        {
          'foo': <Foo />,
          'bar': <Bar />
        }[param]
      }
      <div>
        {/* removed for brevity */}
      </div>
    </div>
  )
}
Lenkan
fuente
1
Esto está muy bien. Lo modifiqué ligeramente para usar una matriz en lugar de un POJO y solo estoy usando el índice para incluir en la matriz.
Nicholas Gentile
1
Esta es la forma estándar de manejar switch-case en Python. Me gusta más en este caso debido a su legibilidad superior.
Me comeré mi sombrero
12
Este enfoque tiene sus límites y sus gastos generales. Cada una de sus vistas será procesada y dependerá del estado actual / accesorios que podrían no existir. Ej .: digamos que desea renderizar: <SearchResults />o <NoResults />. Si el estado de la vista debería representar<NoResults /> , es <SearchResults />posible que no se compile porque depende de propiedades que aún no existen.
ABCD.ca
3
¿Qué pasa con un caso predeterminado?
kungfooman
4
@ lama12345 Para el caso predeterminado, utilice ||lo siguiente:{ 'foo': <Foo />, 'bar': <Bar /> }[param] || <Baz />
Aaron Adrian
54

Eso está sucediendo, porque la switchdeclaración es una statement, pero aquí javascript espera una expresión.

Aunque, no se recomienda utilizar la declaración de cambio en una render método, puede utilizar la función de autoinvocación para lograr esto:

render() {
    // Don't forget to return a value in a switch statement
    return (
        <div>
            {(() => {
                switch(...) {}
            })()}
        </div>
    );
}
1ven
fuente
Gracias, usé eso como: render () {return (<div> {(() => {switch (this.state.type) {case commons.HARD_SOFT: return <HardSoft params = {this.state.param} onSubmitHead = {this.onSubmit} />;}}) ()} </div>);
JhonQO
22

Hice esto dentro del método render ():

  render() {
    const project = () => {
      switch(this.projectName) {

        case "one":   return <ComponentA />;
        case "two":   return <ComponentB />;
        case "three": return <ComponentC />;
        case "four":  return <ComponentD />;

        default:      return <h1>No project match</h1>
      }
    }

    return (
      <div>{ project() }</div>
    )
  }

Traté de mantener limpio el retorno de render (), así que puse mi lógica en una función 'const' justo arriba. De esta manera también puedo sangrar perfectamente mis cajas de interruptores.

williamsi
fuente
@a_m_dev En lugar de una función 'const project' dentro del método render, podemos colocarla como un método componente, luego llamarla dentro render return como "<div> {this.project ()} </div>". A menos que esté hablando de no usar switch en absoluto, entonces puedo pensar en usar if / else, o mostrar / ocultar componentes usando className actualizando el estado.
williamsi
Eso podría ser aún más bueno, porque, por ejemplo, uso un head()método de los componentes de mi ruta para inyectar los datos react-helmeten el encabezado de mi documento
a_m_dev
15

La respuesta de Lenkan es una gran solución.

<div>
  {{ beep: <div>Beep</div>,
     boop: <div>Boop</div>
  }[greeting]}
</div>

Si necesita un valor predeterminado, incluso puede hacerlo

<div>
  {{ beep: <div>Beep</div>,
     boop: <div>Boop</div>
  }[greeting] || <div>Hello world</div>}
</div>

Alternativamente, si eso no le parece bien, puede hacer algo como

<div>
  { 
    rswitch(greeting, {
      beep: <div>Beep</div>,
      boop: <div>Boop</div>,
      default: <div>Hello world</div>
    }) 
  }
</div>

con

function rswitch (param, cases) {
  if (cases[param]) {
    return cases[param]
  } else {
    return cases.default
  }
}
Luke Burns
fuente
2
{{key1: <Component1 />, ...} [key] no es una buena solución. Ves, antes de que ocurra la selección, todo el objeto inicial se construye - es decir, todas las ramas del interruptor se rinde - Componente1, Componente2, etc ...
Gleb Varenov
Sí, la respuesta de lenkan debería ser la respuesta correcta, porque el interruptor no debería usarse en el componente funcional. Gracias por agregar OR para el caso predeterminado. Y no se moleste con rswitch (), ¡la solución del mapa es perfecta! pulgar hacia arriba
Eugenijus S.
15

Una forma de representar una especie de cambio en un bloque de renderizado, usando operadores condicionales:

{(someVar === 1 &&
    <SomeContent/>)
|| (someVar === 2 &&
    <SomeOtherContent />)
|| (this.props.someProp === "something" &&
    <YetSomeOtherContent />)
|| (this.props.someProp === "foo" && this.props.someOtherProp === "bar" &&
    <OtherContentAgain />)
||
    <SomeDefaultContent />
}

Debe asegurarse que las condiciones devuelvan estrictamente un booleano.

arvymetal
fuente
2
Agradable y elegante y se puede usar directamente en el bloque de renderizado. Mejor respuesta en mi humilde opinión
GavinBelson
2
Noté que esto es una violación de las reglas de EsLints eslint.org/docs/rules/no-mixed-operators mezclando && y ||
FishFingers
1
@FishFingers También lo noté cuando intenté usarlo exactamente como se muestra arriba. Puede evitarse fácilmente envolviendo cada "caso" entre paréntesis.
Ch0sen
13

No soy un gran admirador de ninguna de las respuestas actuales, porque son demasiado detalladas o requieren que salte el código para comprender lo que está sucediendo.

Prefiero hacer esto de una manera más centrada en el componente de reacción, creando un archivo <Switch/>. El trabajo de este componente es tomar un accesorio y solo renderizar los niños cuyo accesorio hijo coincida con este. Entonces, en el ejemplo a continuación, he creado un testaccesorio en el interruptor y lo comparé con un valueaccesorio en los niños, solo renderizando los que coinciden.

Ejemplo:

const Switch = props => {
  const { test, children } = props
  // filter out only children with a matching prop
  return children.find(child => {
    return child.props.value === test
  })      
}

const Sample = props => {
  const someTest = true
  return (
    <Switch test={someTest}>
      <div value={false}>Will display if someTest is false</div>
      <div value={true}>Will display if someTest is true</div>
    </Switch>
  )
}

ReactDOM.render(
  <Sample/>,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Puede hacer que el cambio sea tan simple o tan complejo como desee. No olvide realizar una verificación más sólida de los niños y sus apoyos de valor.

Matt Way
fuente
3
¡Exactamente lo que estaba buscando! Aquí hay una pequeña mejora: de javascript const Switch = props => { const { test, children } = props; return children.find(child => { return child.props.value === test; }); }; const Case = ({ children, value }) => { return children; // I don't want do add container around my cases ! }; esa manera puede escribir:javascript <Switch test={true}> <Case value={true}> <ItAlwaysFeelsRightToBeTrue /> </Case> <Case value={false}> <FalseAlarm /> </Case> </Switch>
lsmod
Si bien esta es una buena solución, agregar una explicación al código lo habría hecho aún mejor
papigee
@papigee Actualizado con un poco más de detalle.
Matt Way
@MattWay Si bien esta es una buena opción para JS puro, arroja un error de TS cuando el objetivo se establece en es5 ya que la búsqueda de propiedad no existe en ReactNode
Zakriya Bilal
@ZakriyaBilal El código es más una prueba de concepto y sobre cómo las ideas de componentes como interruptores harán que su código reaccione más. La clave es encontrar una manera de comparar los accesorios de los niños.
Matt Way
3

Qué tal si:

mySwitchFunction = (param) => {
   switch (param) {
      case 'A':
         return ([
            <div />,
         ]);
      // etc...
   }
}
render() {
    return (
       <div>
          <div>
               // removed for brevity
          </div>

          { this.mySwitchFunction(param) }

          <div>
              // removed for brevity
          </div>
      </div>
   );
}
James
fuente
2

Puedes hacer algo como esto.

 <div>
          { object.map((item, index) => this.getComponent(item, index)) }
 </div>

getComponent(item, index) {
    switch (item.type) {
      case '1':
        return <Comp1/>
      case '2':
        return <Comp2/>
      case '3':
        return <Comp3 />
    }
  }
monkeyjs
fuente
2

No puede tener un interruptor en el render. El enfoque psuedo-switch de colocar un objeto literal que accede a un elemento no es ideal porque hace que todas las vistas se procesen y eso puede resultar en errores de dependencia de accesorios que no existen en ese estado.

Aquí hay una forma limpia y agradable de hacerlo que no requiere que cada vista se renderice por adelantado:

render () {
  const viewState = this.getViewState();

  return (
    <div>
      {viewState === ViewState.NO_RESULTS && this.renderNoResults()}
      {viewState === ViewState.LIST_RESULTS && this.renderResults()}
      {viewState === ViewState.SUCCESS_DONE && this.renderCompleted()}
    </div>
  )

Si sus condiciones para qué estado de vista se basan en más que una propiedad simple, como múltiples condiciones por línea, entonces una enumeración y una getViewStatefunción para encapsular las condiciones es una buena manera de separar esta lógica condicional y limpiar su renderizado.

ABCD.ca
fuente
Manera sencilla y limpia.
Abhilekh Singh
2

Aquí hay un ejemplo de trabajo completo usando un botón para cambiar entre componentes

puede configurar un constructor de la siguiente manera

constructor(props)
{
    super(props);
    this.state={
        currentView: ''
    }
}

entonces puedes renderizar componentes de la siguiente manera

  render() 
{
    const switchView = () => {

    switch(this.state.currentView) 
    {

      case "settings":   return <h2>settings</h2>;
      case "dashboard":   return <h2>dashboard</h2>;

      default:      return <h2>dashboard</h2>
    }
  }

    return (

       <div>

            <button onClick={(e) => this.setState({currentView: "settings"})}>settings</button>
            <button onClick={(e) => this.setState({currentView: "dashboard"})}>dashboard</button>

            <div className="container">
                { switchView() }
            </div>


        </div>
    );
}

}

Como puede ver, estoy usando un botón para cambiar entre estados.

Jerryurenaa
fuente
1

Realmente me gustó la sugerencia en https://stackoverflow.com/a/60313570/770134 , así que la adapté a Typecript así

import React, { FunctionComponent } from 'react'
import { Optional } from "typescript-optional";
const { ofNullable } = Optional

interface SwitchProps {
  test: string
  defaultComponent: JSX.Element
}

export const Switch: FunctionComponent<SwitchProps> = (props) => {
  return ofNullable(props.children)
    .map((children) => {
      return ofNullable((children as JSX.Element[]).find((child) => child.props['value'] === props.test))
        .orElse(props.defaultComponent)
    })
    .orElseThrow(() => new Error('Children are required for a switch component'))
}

const Foo = ({ value = "foo" }) => <div>foo</div>;
const Bar = ({ value = "bar" }) => <div>bar</div>;
const value = "foo";
const SwitchExample = <Switch test={value} defaultComponent={<div />}>
  <Foo />
  <Bar />
</Switch>;
fieldju
fuente
-1

Este es otro enfoque.

render() {
   return {this[`renderStep${this.state.step}`]()}

renderStep0() { return 'step 0' }
renderStep1() { return 'step 1' }
Rodrigo Hernán Ramos
fuente