【问题标题】:How to correctly wrap JSX elements and display JSON depending on selection如何正确包装 JSX 元素并根据选择显示 JSON
【发布时间】:2019-08-28 08:53:34
【问题描述】:

我正在使用 React 开发一个可以以不同语言显示的大学管理网站。到目前为止,我已经开发了如下所示的登录页面:

此时,我正在尝试以所选语言(EU、ES、EN)显示网站,但我收到 Module build failed 错误:

我真的不明白为什么会出现这个错误,因为我认为 JSX/html 标签被正确包装(注意我使用的是 Babel),请查看代码:

Login.js

import React from 'react'
import PropTypes from 'prop-types'
import { fetchPopularRepos } from '../utils/api'

function LanguagesNav ({ selected, onUpdateLanguage}) {
  const languages = ['EU', 'ES', 'EN']

  return (
    <div >
      <h1 className='center-text header-lg'>
        GAUR 2.0
      </h1>
      <ul className='flex-center'>
        {languages.map((language) => (
          <li key={language}>
            <button
              className='btn-clear nav-link'
              style={language === selected ? { color: 'rgb(187, 46, 31)' } : null }
              onClick={() => onUpdateLanguage(language)}>
              {language}
            </button>
          </li>
        ))}
      </ul>
    </div>
  )
}

LanguagesNav.propTypes = {
  selected: PropTypes.string.isRequired,
  onUpdateLanguage: PropTypes.func.isRequired
}

function LoginForm ({ repos, selectedLanguage }) {
  return (
    <form className='column player'>
      {(repos, selectedLanguage) => {
        var lang = { username, password, login }
        switch (selectedLanguage) {
          case 0:
            selectedLanguage = "EU";
            lang  = repos[0]
            break;
          case 1:
            selectedLanguage = "ES";
            lang = repos[1]
            break;
          case 2:
            selectedLanguage = "EN";
            lang = repos[2]
            break;
        }

        return (
          <div className='row player-inputs'>
            <input
              type='text'
              id='username'
              className='input-light'
              placeholder={lang.username}
              autoComplete='off'
            />
          </div>
          <div className='row player-inputs'>
            <input
              type='password'
              id='password'
              className='input-light'
              placeholder={lang.password}
              autoComplete='off'
            />
          </div>
          <div className='row player-inputs'>
            <button
              className='btn dark-btn'
              type='submit'
            >
              {lang.login}
            </button>
          </div>
        )
      }}
    </form>
  )
}

export default class Login extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      selectedLanguage: 'EU',
      repos: null,
      error: null
    }

    this.updateLanguage = this.updateLanguage.bind(this)
    this.isLoading = this.isLoading.bind(this)
  }
  componentDidMount () {
    this.updateLanguage(this.state.selectedLanguage)
  }
  updateLanguage (selectedLanguage) {
    this.setState({
      selectedLanguage,
      error: null,
      repos: null
    })

    fetchPopularRepos(selectedLanguage)
      .then((repos) => this.setState({
          repos,
          error: null,
      }))
      .catch(() => {
        console.warn('Error fetching repos: ', error)

        this.setState({
          error: 'There was an error fetching the repositories.'
        })
      })
  }
  isLoading() {
    return this.state.repos === null && this.state.error === null
  }
  render() {
    const { selectedLanguage, repos, error } = this.state

    return (
      <React.Fragment>
        <LanguagesNav
          selected={selectedLanguage}
          onUpdateLanguage={this.updateLanguage}
        />
        {this.isLoading() && <p>LOADING...</p>}
        {error && <p>{error}</p>}
        <LoginForm repos={repos, selectedLanguage} />
      </React.Fragment>
    )
  }

}

我从中检索数据的 JSON 文件:

languagesdata.js

{
  "languages": [
    {
      "language": "EU",
      "terms": {
        "username": "Erabiltzailea",
        "password": "Pasahitza",
        "welcome": "Sartu GAUR 2.0ra",
        "login": "Sartu"
      }
    },
    {
      "language": "ES",
      "terms": {
        "username": "Usuario",
        "password": "Contraseña",
        "welcome": "Entra a GAUR 2.0",
        "login": "Entrar"
      }
    },
    {
      "language": "EN",
      "terms": {
        "password": "Password",
        "username": "Username",
        "welcome": "Log into GAUR 2.0",
        "login": "Log in"
      }
    }
  ]
}

使用 API:

api.js

export function fetchPopularRepos () {
  const endpoint = 'app/languagesdata.json'

  return fetch(endpoint)
    .then((res) => res.json())
    .then((data) => {
      if (!data.languages) {
        throw new Error(data.message)
      }

      return data.languages
    })
}
  • 错误的真正原因是什么?

  • 根据 React 的选择显示 JSON 数据的正确方法是什么?

【问题讨论】:

  • 感谢您的评论,但问题已经结束。

标签: javascript json reactjs jsx babeljs


【解决方案1】:

错误是因为这个,

return (
  <div className='row player-inputs'>
    <input
      type='text'
      id='username'
      className='input-light'
      placeholder={lang.username}
      autoComplete='off'
    />
  </div>
  <div className='row player-inputs'>
    <input
      type='password'
      id='password'
      className='input-light'
      placeholder={lang.password}
      autoComplete='off'
    />
  </div>
  <div className='row player-inputs'>
    <button
      className='btn dark-btn'
      type='submit'
    >
      {lang.login}
    </button>
  </div>
)

您在此处返回多个元素。只需将其包装在 Fragment (&lt;&gt;&lt;/&gt;) 中

return (
  <>
  <div className='row player-inputs'>
    <input
      type='text'
      id='username'
      className='input-light'
      placeholder={lang.username}
      autoComplete='off'
    />
  </div>
  <div className='row player-inputs'>
    <input
      type='password'
      id='password'
      className='input-light'
      placeholder={lang.password}
      autoComplete='off'
    />
  </div>
  <div className='row player-inputs'>
    <button
      className='btn dark-btn'
      type='submit'
    >
      {lang.login}
    </button>
  </div>
  </>
)

【讨论】:

    【解决方案2】:

    函数必须返回单个 DOM 元素或片段。

    深入了解您在此处返回的内容

    function LoginForm ({ repos, selectedLanguage }) {
    //...
            return (
              <div className='row player-inputs'>
                <input
                  type='text'
                  id='username'
                  className='input-light'
                  placeholder={lang.username}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <input
                  type='password'
                  id='password'
                  className='input-light'
                  placeholder={lang.password}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <button
                  className='btn dark-btn'
                  type='submit'
                >
                  {lang.login}
                </button>
              </div>
            )
          }}
        //...
    

    您可以将所有内容包装到另一个元素中,也可以使用&lt;React.Fragment&gt;&lt;/React.Fragment&gt;(或&lt;&gt;&lt;/&gt;)。

    【讨论】:

      【解决方案3】:

      将render() 中返回的内容包装在&lt;React.Fragment&gt; 标记中。

      return (
        <React.Fragment>
          <div className='row player-inputs'>
            <input
              type='text'
              id='username'
              className='input-light'
              placeholder={lang.username}
              autoComplete='off'/>
          </div>
          <div className='row player-inputs'>
            <input
              type='password'
              id='password'
              className='input-light'
              placeholder={lang.password}
              autoComplete='off'/>
          </div>
          <div className='row player-inputs'>
            <button
              className='btn dark-btn'
              type='submit'>
              {lang.login}
            </button>
          </div>
        </React.Fragment>
      )
      

      就像错误消息说:“相邻标签必须包装在[父]标签中”。

      【讨论】:

        【解决方案4】:

        此处出错:

        return (
         <div className='row player-inputs'>
                <input
                  type='text'
                  id='username'
                  className='input-light'
                  placeholder={lang.username}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <input
                  type='password'
                  id='password'
                  className='input-light'
                  placeholder={lang.password}
                  autoComplete='off'
                />
              </div>
              ...
        

        你应该把它包装成一个标签,比如&lt;div&gt;

        例如:

            return (
        <div>
              <div className='row player-inputs'>
                <input
                  type='text'
                  id='username'
                  className='input-light'
                  placeholder={lang.username}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <input
                  type='password'
                  id='password'
                  className='input-light'
                  placeholder={lang.password}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <button
                  className='btn dark-btn'
                  type='submit'
                >
                  {lang.login}
                </button>
              </div>
        </div>
            )
        

        【讨论】:

        • 感谢您的回答。我认为您的答案是有效的,但是,我认为最好用 标记将其包装起来,就像其他答案所建议的那样。
        • 是的,你是对的。这确实是比我最好的解决方案,因为它消除了添加冗余
          的需要
        【解决方案5】:

        错误的真正原因是什么?

        组件返回多个 JSX 元素。但不包含在单个父 JSX 元素中。所以,按照错误提示使用React.Fragments。

        return (
          <> // shorthand syntax of React.Fragment
            {children}
          </>
        );
        

        根据 React 的选择显示 JSON 数据的正确方法是什么?

        据我检查,您正确地遵循了 React 的 Lifting State Up 状态。

        状态(即selectedLanguage)存储在父组件中并传递给&lt;LoginForm/&gt;。

        【讨论】:

        • 感谢您回答我的第一个问题。关于第二个问题的答案,如果我错了,请纠正我,但我认为映射不适用于我想做的事情,因为我需要传递所选语言并根据所选语言传递 JSON 项。跨度>
        【解决方案6】:

        我使您的代码按预期工作,请运行并告诉我您的反馈

        变化

        1. 包装的元素返回 React.Fragment
        2. getSelectedLang 引入了新方法来处理 selectedLang 而不是在 LoginForm 中进行
        3. Lo​​ginForm 更改为类内的成员
        4. 直接注入响应而不是请求获取数据

        查看代码 - https://repl.it/repls/NaturalFarDifferences

        import ReactDOM from "react-dom";
        import React from "react";
        
        import PropTypes from "prop-types";
        //import { fetchPopularRepos } from '../utils/api'
        
        function LanguagesNav({ selected, onUpdateLanguage }) {
          const languages = ["EU", "ES", "EN"];
        
          return (
            <div>
              <h1 className="center-text header-lg">GAUR 2.0</h1>
              <ul className="flex-center">
                {languages.map(language => (
                  <li key={language}>
                    <button
                      className="btn-clear nav-link"
                      style={
                        language === selected ? { color: "rgb(187, 46, 31)" } : null
                      }
                      onClick={() => onUpdateLanguage(language)}
                    >
                      {language}
                    </button>
                  </li>
                ))}
              </ul>
            </div>
          );
        }
        
        LanguagesNav.propTypes = {
          selected: PropTypes.string.isRequired,
          onUpdateLanguage: PropTypes.func.isRequired
        };
        
        
        
        export default class Login extends React.Component {
          constructor(props) {
            super(props);
        
            this.state = {
              selectedLanguage: "EU",
              repos: null,
              error: null
            };
        
            this.updateLanguage = this.updateLanguage.bind(this);
            this.isLoading = this.isLoading.bind(this);
            this.LoginForm = this.LoginForm.bind(this);
          }
          getSelectedLang  = (repos, selectedLanguage) => {
        
                console.log('repos', repos);
                  console.log('selectedLanguage', selectedLanguage);
                var lang = "";
                switch (selectedLanguage) {
                  case "EU":
                    selectedLanguage = "EU";
                    lang = repos && repos[0];
                    break;
                  case "ES":
                    selectedLanguage = "ES";
                    lang = repos && repos[1];
                    break;
                  case "EN":
                    selectedLanguage = "EN";
                    lang = repos && repos[2];
                    break; 
                    default: 
                    break;
                }
        
                return (
                  <React.Fragment>
                    <div className="row player-inputs">
                      <input
                        type="text"
                        id="username"
                        className="input-light"
                        placeholder={lang &&lang.terms.username}
                        autoComplete="off"
                      />
                    </div>
                    <div className="row player-inputs">
                      <input
                        type="password"
                        id="password"
                        className="input-light"
                        placeholder={lang &&lang.terms.password}
                        autoComplete="off"
                      />
                    </div>
                    <div className="row player-inputs">
                      <button className="btn dark-btn" type="submit">
                        {lang && lang.terms.login}
                      </button>
                    </div>
                  </React.Fragment>
                );
        
          }
          LoginForm = (repos, selectedLanguage) => {
           // let { repos, selectedLanguage } = data;
        
          return (
            <form className="column player">
              {this.getSelectedLang(repos, selectedLanguage)}
            </form>
          );
        }
          componentDidMount() {
            this.updateLanguage(this.state.selectedLanguage);
           // console.log("heoocds");
          }
          updateLanguage = (selectedLanguage) => {
            //console.log(selectedLanguage);
           // console.log("heoocds");
        
            let obj = 
                [{
                  language: "EU",
                  terms: {
                    username: "Erabiltzailea",
                    password: "Pasahitza",
                    welcome: "Sartu GAUR 2.0ra",
                    login: "Sartu"
                  }
                },
                {
                  language: "ES",
                  terms: {
                    username: "Usuario",
                    password: "Contraseña",
                    welcome: "Entra a GAUR 2.0",
                    login: "Entrar"
                  }
                },
                {
                  language: "EN",
                  terms: {
                    password: "Password",
                    username: "Username",
                    welcome: "Log into GAUR 2.0",
                    login: "Log in"
                  }
                } ]
                this.setState({
                  selectedLanguage,
                  error: null,
                  repos: obj
                });
            };
        
        
            // fetchPopularRepos(selectedLanguage)
            //   .then((repos) => this.setState({
            //       repos,
            //       error: null,
            //   }))
            //   .catch(() => {
            //     console.warn('Error fetching repos: ', error)
        
            //     this.setState({
            //       error: 'There was an error fetching the repositories.'
            //     })
            //   })
        
          isLoading = () => {
            return this.state.repos === null && this.state.error === null;
          }
          render() {
            const { selectedLanguage, repos, error } = this.state;
            // console.log(repos);
            // console.log(selectedLanguage)
            return (
              <React.Fragment>
                <LanguagesNav
                  selected={selectedLanguage}
                  onUpdateLanguage={this.updateLanguage}
                />
                {this.isLoading() && <p>LOADING...</p>}
                {error && <p>{error}</p>}
                {this.LoginForm(repos, selectedLanguage)}
        
              </React.Fragment>
            );
          }
        }
        
        ReactDOM.render(<Login />, document.getElementById("root"));
        

        【讨论】:

        • 感谢您的回答,但是,即使您的解决方案可能有效,我也不认为它有效,因为您已经对整个代码进行了非结构化并将 JSON 数据放入对象中。请看我的回答,因为我已经设法开发出一个解决方案,可以在保持代码良好的同时满足我的要求。
        • 感谢您的反馈
        【解决方案7】:

        我已经设法自己解决了这个问题。我没有正确传递道具,因为我试图在 LoginForm 函数中接收 2 个道具,而我只传递了 1 个道具并且我没有检查 repos 是否为空:

        <LoginForm repos={repos, selectedLanguage} />
        

        解决方案:

        {repos && <LoginForm repos={repos} selectedLanguage={selectedLanguage} />}
        

        我设法解决了这个问题,问了另一个问题:How to pass an array as props in React without being converted to string

        然后我在 LoginForm 函数中开发了一个逻辑,以显示选择的语言。

        完整的解决方案:

        import React from 'react'
        import PropTypes from 'prop-types'
        import { fetchPopularRepos } from '../utils/api'
        
        function LanguagesNav ({ selected, onUpdateLanguage}) {
          const languages = ['EU', 'ES', 'EN']
        
          return (
            <div >
              <h1 className='center-text header-lg'>
                GAUR 2.0
              </h1>
              <ul className='flex-center'>
                {languages.map((language) => (
                  <li key={language}>
                    <button
                      className='btn-clear nav-link'
                      style={language === selected ? { color: 'rgb(187, 46, 31)' } : null }
                      onClick={() => onUpdateLanguage(language)}>
                      {language}
                    </button>
                  </li>
                ))}
              </ul>
            </div>
          )
        }
        
        LanguagesNav.propTypes = {
          selected: PropTypes.string.isRequired,
          onUpdateLanguage: PropTypes.func.isRequired
        }
        
        function LoginForm ({ repos, selectedLanguage }) {
          var lang = {}
          switch (selectedLanguage) {
            case "EU":
              selectedLanguage = "EU";
              lang  = repos[0].terms;
              break;
            case "ES":
              selectedLanguage = "ES";
              lang = repos[1].terms;
              break;
            case "EN":
              selectedLanguage = "EN";
              lang = repos[2].terms;
              break;
          }
        
          return (
            <form className='column player'>
              <div className='row player-inputs'>
                <input
                  type='text'
                  id='username'
                  className='input-light'
                  placeholder={lang.username}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <input
                  type='password'
                  id='password'
                  className='input-light'
                  placeholder={lang.password}
                  autoComplete='off'
                />
              </div>
              <div className='row player-inputs'>
                <button
                  className='btn dark-btn'
                  type='submit'
                >
                  {lang.login}
                </button>
              </div>
            </form>
          )
        }
        
        LoginForm.propTypes = {
          repos: PropTypes.array.isRequired
        }
        
        export default class Login extends React.Component {
          constructor(props) {
            super(props)
        
            this.state = {
              selectedLanguage: 'EU',
              repos: null,
              error: null
            }
        
            this.updateLanguage = this.updateLanguage.bind(this)
            this.isLoading = this.isLoading.bind(this)
          }
          componentDidMount () {
            this.updateLanguage(this.state.selectedLanguage)
          }
          updateLanguage (selectedLanguage) {
            this.setState({
              selectedLanguage,
              error: null,
              repos: null
            })
        
            fetchPopularRepos(selectedLanguage)
              .then((repos) => this.setState({
                  repos,
                  error: null,
              }))
              .catch(() => {
                console.warn('Error fetching repos: ', error)
        
                this.setState({
                  error: 'There was an error fetching the repositories.'
                })
              })
          }
          isLoading() {
            return this.state.repos === null && this.state.error === null
          }
          render() {
            const { selectedLanguage, repos, error } = this.state
        
            return (
              <React.Fragment>
                <LanguagesNav
                  selected={selectedLanguage}
                  onUpdateLanguage={this.updateLanguage}
                />
        
                {this.isLoading() && <p>LOADING...</p>}
        
                {error && <p>{error}</p>}
        
                {repos && <LoginForm repos={repos} selectedLanguage={selectedLanguage} />}
        
              </React.Fragment>
            )
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2019-08-29
          • 1970-01-01
          • 1970-01-01
          • 2021-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-02-06
          相关资源
          最近更新 更多