简体   繁体   中英

How do I conditionally render react functional components with switch statement?

I am trying to render a multipage form to display a particular page based on the step variable with a switch statement. To my dismay, nothing is coming out after the call that renders the switch statement am I missing something?

import React, { useState } from "react";

import { songValidationSchema } from "../../../../utilities/validation";
import { Form } from "../../../../utilities/components/form";
import SONG_CAPTURE_PROPERTIES from "../../../../utilities/constants/SongCaptureProperties";
import { groupSongMetaProperties } from "../../../../utilities/functions";
import SongDetailsPage from "./SongDetailsPage";

import "./SongUpload.css";

const SongUpload = () => {
  const [step, setStep] = useState(1);

  const groupedSongMetaProperties = groupSongMetaProperties(
    SONG_CAPTURE_PROPERTIES,
    4 // Into arrays of max length of 4.
  );

  const initialValues = {
    song: "",
    art: "",
    trackName: "",
    genre: "",
    album: "",
    artist: "",
    publisher: "",
    recordLabel: "",
    releaseYear: "",
    copyrightMessage: "",
    subtittle: "",
    comment: "",
    unsynchronisedLyric: "",
  };

  const nextStep = () => setStep(step + 1);
  const previousStep = () => setStep(step - 1);

  const handleSubmit = (values) => console.log("Form Data: ", values);

  const renderSongDetailPage = () => {
    switch (step) {
      case 1:
        return (
          <SongDetailsPage
            inputNames={groupedSongMetaProperties[0]}
            nextStep={nextStep}
            previousStep={previousStep}
          />
        );
      case 2:
        return (
          <SongDetailsPage
            inputNames={groupedSongMetaProperties[1]}
            nextStep={nextStep}
            previousStep={previousStep}
          />
        );
      case 3:
        return (
          <SongDetailsPage
            inputNames={groupedSongMetaProperties[2]}
            nextStep={nextStep}
            previousStep={previousStep}
          />
        );
      case 4:
        return (
          <SongDetailsPage
            inputNames={groupedSongMetaProperties[3]}
            nextStep={nextStep}
            previousStep={previousStep}
          />
        );
    }
  };

  return (
    <Form
      initialValues={initialValues}
      onSubmit={handleSubmit}
      validationSchema={songValidationSchema}
    >
      <h1>SongUpload</h1>
      <h3>Upload Your Songs here!</h3>

      {renderSongDetailPage() // This is the function calling the switch Render.} 
    </Form>
  );
};
export default SongUpload;

The Component below that is as follows:

import React from "react";

import { FormInput } from "../../../common/forms/form";

const SongDetailsPage = ({ inputNames, nextStep, previousStep, step }) => {
  const getInputType = (str, step) => {
    let type = "";
    switch (step) {
      case 1:
        switch (str) {
          case "song_file":
            type = "file";
            break;
          case "song_art":
            type = "image";
            break;
          case "track_name":
            type = "text";
            break;
          case "genre":
            type = "text";
            break;
          default:
            type = null;
            break;
        }
        break;
      case 2:
        type = "text"; // Since all fields need text in this group
        break;
      case 3:
        switch (str) {
          case "release_year":
            type = "date";
            break;
          case "subtittle":
            type = "text";
            break;
          default:
            type = null;
            break;
        }
        break;
      case 4:
        // case "unsynchronised_lyric": -> The only case in the last one
        type = null;
    }

    return type;
  };

  if (inputNames == null) return;
  else
    return (
      <span className="input-wrapper">
        {inputNames.map((key, index) => {
          const type = getInputType(inputNames[index], step);
          if (type !== null)
            <span key={key}>
              <FormInput
                label={inputNames[index]}
                name={
                  inputNames[index] === "song_file"
                    ? "song"
                    : inputNames[index] === "song_art"
                    ? "art"
                    : inputNames[index] === "track_name"
                    ? "trackName"
                    : inputNames[index]
                }
                type={type}
              />
              ;
            </span>;
          else <span key={key}> Yo! </span>;
        })}
      </span>
    );
};

export default SongDetailsPage;

And the last component is:

import { faEye, faEyeLowVision } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useEffect, useRef, useState } from "react";

import "./Form.css";

const FormInput = ({
  accept,
  icon,
  id,
  label,
  onBlur,
  onChange,
  name,
  type,
  ...otherProps
}) => {
  const [passwordRevealed, setPasswordRevealed] = useState(false);

  const inputElement = useRef(null);
  const labelElement = useRef(null);

  useEffect(() => {
    // input-in-focus css class moves the label out of the field and shrinks it
    if (inputElement.current.value !== "") {
      labelElement.current.classList.add("input-in-focus");
    }
  });

  const handleRevealPassword = () => {
    setPasswordRevealed(!passwordRevealed);
    if (!passwordRevealed) type = "text";
    else type = "password";
    inputElement.current.type = type;
  };

  const handleFocus = () => {
    if (
      document.activeElement === inputElement.current ||
      inputElement.current.value !== ""
    )
      labelElement.current.classList.add("input-in-focus");
    else labelElement.current.classList.remove("input-in-focus");
  };

  const handleBlur = (e) => {
    if (e.target.type === "file") return;
    handleFocus();
    onBlur(e);
  };

  return (
    <div className="form-input-container">
      {icon && (
        <span className="label-icon-container">
          <FontAwesomeIcon
            className="form-input-icon"
            // color={defaultStyles.colors.tomato}
            icon={icon}
            size="lg"
          />
        </span>
      )}

      <label>
        <p ref={labelElement}>{label}</p>
        <input
          accept={accept}
          id={id}
          name={name}
          onFocus={handleFocus}
          onBlur={handleBlur}
          onChange={onChange}
          ref={inputElement}
          type={type}
          {...otherProps}
        />
      </label>

      {type === "password" && (
        <span className="upendi-password-eye">
          <FontAwesomeIcon
            className="password-eye"
            // color={defaultStyles.colors.tomato}
            icon={passwordRevealed ? faEyeLowVision : faEye}
            onClick={handleRevealPassword}
            size="lg"
            title={
              passwordRevealed
                ? "Click To Hide Password"
                : "Click To Reveal Password"
            }
          />
        </span>
      )}
    </div>
  );
};

export default FormInput;

I'm not 100% sure about the validity of this suggestion so others should weigh in. But in order to achieve what you want, you will need to return a function instead of jsx from your renderSongDetailPage and pass the props as an object to the component. Then call renderSongDetailPage as a jsx element. Remember that renderSongDetailPage needs to RenderSongDetailPage with a capital R .

const RenderSongDetailPage = () => {
  switch (step) {
    case 1:
      return SongDetailsPage({
        inputNames: groupedSongMetaProperties[0],
        nextStep: nextStep,
        previousStep: previousStep,
      });
    case 2:
      return SongDetailsPage({
        inputNames: groupedSongMetaProperties[1],
        nextStep: nextStep,
        previousStep: previousStep,
      });
    case 3:
      return SongDetailsPage({
        inputNames: groupedSongMetaProperties[2],
        nextStep: nextStep,
        previousStep: previousStep,
      });
    case 4:
      return SongDetailsPage({
        inputNames: groupedSongMetaProperties[3],
        nextStep: nextStep,
        previousStep: previousStep,
      });
  }
};

Usage:

<RenderSongDetailPage/>

If I can make a suggestion through observation. The only thing changing in that RenderSongDetailPage function is the array item for groupedSongMetaProperties so you could possible just render in the main return as follows:

<SongDetailsPage
  inputNames={groupedSongMetaProperties[step-1]}
  nextStep={nextStep}
  previousStep={previousStep}
/>;

which should give you the same results. Although I do not know the entire extent of your project so just a suggestion.

Ultimately what you would see in the wild is just a bunch of if statements each returning a SongDetailsPage

A code sandbox: https://codesandbox.io/s/jolly-gagarin-n66xp9?file=/src/App.js

The issue was in the SongDetailsPage component. I was not returning inside each map iteration.

import {
  faCalendar,
  faComment,
  faCompactDisc,
  faCopyright,
  faFileAudio,
  faFileWord,
  faGuitar,
  faImage,
  faMusic,
  faNewspaper,
  faPenToSquare,
  faRecordVinyl,
  faUser,
} from "@fortawesome/free-solid-svg-icons";
import React from "react";

import { FormInput } from "../../../../utilities/components/form";

const SongDetailsPage = ({ inputNames, step }) => {
  const getInputType = (str, step) => {
    switch (step) {
      case 1:
        switch (str) {
          case "song_file":
            return "file";

          case "song_art":
            return "image";

          default:
            return "text";
        }

      case 2:
        return "text"; // Since all fields need text in this group

      case 3:
        switch (str) {
          case "release_year":
            return "date";

          case "subtitle":
            return "text";

          default:
            return null;
        }
      case 4:
        // case "unsynchronised_lyric": -> The only case in the last one
        return null;
    }
  };

  if (!inputNames) return;
  else
    return (
      <>
        {inputNames.map((key, index) => {
          const type = getInputType(inputNames[index], step);
          return ( // <- This return statement was the issue.
            ...
          );
        })}
      </>
    );
};

export default SongDetailsPage;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM