【问题标题】:Component not re-rendering on state change组件不会在状态更改时重新渲染
【发布时间】:2019-03-05 17:44:46
【问题描述】:

首先,我在这里查看了许多与此类似的问题,但找不到解决方案。基本上在我的 App.js 中,我有两个按钮可以在英语(en)和西班牙语(es)之间更改this.state.language。当我对语言状态进行硬编码时,它会起作用。 (我通过 JSON 文件获取语言数据)。

但是,现在,当我尝试使用这两个按钮时,状态会发生变化(通过控制台日志验证),但页面上的语言不会更新。在下面的页面上,我有一个 if 语句来检测语言状态,并从那里选择从 JSON 文件中的哪个位置获取数据。请有人向我解释为什么这不起作用?

App.js

class App extends Component {
  constructor(){
    super();
    this.state = {
      sideNav: '',
      language: ''
    }
    this.langEn = this.langEn.bind(this);
    this.langEs = this.langEs.bind(this);
  }

  langEn() {
    this.setState({language: 'en'});
    console.log(this.state);
  }

  langEs() {
    this.setState({language: 'es'});
    console.log(this.state);
  }

  render() {

    const mouseEnter = e => {
      this.setState({sideNav: "sideNav sidenav---sidenav---_2tBP sidenav---expanded---1KdUL"});
    }    

    const mouseLeave = e => {
      this.setState({sideNav: "sidenav---sidenav---_2tBP sidenav---collapsed---LQDEv"});
    }


    return (
      <div className="App container">
        <div>
          <SideNav 
            onMouseEnter={mouseEnter} 
            onMouseLeave={mouseLeave}
            className={this.state.sideNav}
            onSelect={(selected) => {
                // Add your code here
            }}
          >
            <SideNav.Nav  defaultSelected="home">
                <NavItem eventKey="home">
                    <NavIcon>
                        <Link to="/"><img src={Dash}/></Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/">Dashboard</Link>
                    </NavText>
                </NavItem>
                <NavItem eventKey="sites">
                    <NavIcon>
                      <Link to="/sites"><img src={Site} /></Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/sites">Sites</Link>
                    </NavText>
                </NavItem>
                <NavItem eventKey="tours">
                  <NavIcon>
                    <Link to="/tours"><img src={Tour}/></Link>
                  </NavIcon>
                  <NavText>
                      <Link to="/tours">Tours</Link>
                  </NavText>
                </NavItem>
                <NavItem eventKey="media">
                    <NavIcon>
                      <Link to="/media"><img src={Media}/> </Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/media">Media</Link>
                    </NavText>
                </NavItem>
                <NavItem eventKey="newSite">
                    <NavIcon>
                        <Link to="/newSite/details"><img src={NewSite} /></Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/newSite/details">Add new Site</Link>
                    </NavText>
                </NavItem>
                <NavItem eventKey="language">
                    <NavIcon>
                        <Link to="/language"><img src={Lang} /></Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/language">Language</Link>
                    </NavText>
                </NavItem>
                <NavItem eventKey="profile">
                    <NavIcon>
                        <Link to="/profile"><img src={Profile} /></Link>
                    </NavIcon>
                    <NavText>
                        <Link to="/profile">Profile</Link>
                    </NavText>
                </NavItem>

            </SideNav.Nav>
            <button onClick={this.langEn}>EN</button>
            <button onClick={this.langEs}>ES</button>
          </SideNav>
        </div>
        <Routes childProps={this.state} />
      </div>
    );
      }
    }

    export default App;

我要翻译的页面:

import React, { Component } from 'react';
import { Col, Button, Form, FormGroup, Label, Input } from 'reactstrap';
import {withRouter} from 'react-router-dom';
import './NewForm.css';
import data from '../data.json';


class NewFormDetails extends Component {
    constructor(props) {
        super(props);

        this.state = {
            language: '',
            siteName: '',
            counties: '',
            siteAddress: '',
            siteEmail: '',
            siteNumber: '',
            siteCat: '',
            openTimes: '',
            fees: '',
            access: '',
            gps: '',
            w3w: '',
            txtHeader: '',
            txtContent: ''
        };

    }

    validateForm() {
        if (this.state.siteName != '' &&
            this.state.siteAddress != '' &&
            this.state.siteEmail != '' &&
            this.state.siteNumber != '' &&
            this.state.openTimes != '' && 
            this.state.fees != '' && 
            this.state.access != '' && 
            this.state.gps != '' && 
            this.state.w3w != '' && 
            this.state.txtHeader != '' && 
            this.state.txtContent != '') {
                return true;
            } else {
                return false;
            }
    }

    handleChange = e => {
        this.setState({ ...this.state, [e.target.name]: e.target.value });
        console.log(this.state);
    }

    handleSubmit = event => {
        event.preventDefault();
        console.log(this.state);
        this.props.history.push('/newSite/tours');
    }

    render() {

        this.setState({language: this.props.language});

        let jsonLang;

        if (this.state.language == 'en') {
            jsonLang = data.en;
        } else if (this.state.language == 'es') {
            jsonLang = data.es;
        } else {
            jsonLang = data.en;
        }

        this.placeholders = jsonLang.placeholders;
        this.counties = jsonLang.counties;
        this.categories = jsonLang.categories;
        console.log(this.state.language)

        return (
            <Form onSubmit={this.handleSubmit} className="form">
                {/* General Information */}
                <FormGroup row>
                    <Col sm={6}>
                        <Input type="text" onChange={this.handleChange} name="siteName" id="siteName" placeholder={this.placeholders.siteName}/>
                    </Col>
                    <Col sm={6}>
                        <Input className="form-control" type="select" id="counties" onChange={this.handleChange}>
                            <option className="selectDefault" disabled value={this.placeholders.siteCounties} selected>{this.placeholders.siteCounty}</option>
                            { this.counties.map(c => (<option key={c.value} value={c.value}>{c.display}</option>))}
                        </Input>
                    </Col>
                </FormGroup>
                <FormGroup row>
                    <Col sm={12}>
                        <Input type="textarea" onChange={this.handleChange} name="siteAddress" placeholder={this.placeholders.siteAdd} id="siteAddress" />
                    </Col>
                </FormGroup>
                <FormGroup row>
                    <Col sm={6}>
                        <Input type="email" name="siteEmail" onChange={this.handleChange} id="siteEmail" placeholder={this.placeholders.email} />
                    </Col> 
                    <Col sm={6}>
                        <Input type="tel" name="siteNumber" onChange={this.handleChange} id="siteNumber" placeholder={this.placeholders.number}/>
                    </Col>
                </FormGroup>
                <FormGroup row>
                    <Col sm={6}>
                        <Input type="select" name="siteCat" onChange={this.handleChange} id="siteCat" multiple placeholder={this.placeholders.categories}>
                            <option className="selectDefault" disabled selected>{this.placeholders.categories}</option>
                            { this.categories.map(c => (<option key={c.value} value={c.value}>{c.display}</option>))}
                        </Input>
                    </Col>
                    <Col sm={6}>
                        <Input type="textarea" name="openTimes" onChange={this.handleChange} id="openTimes" placeholder={this.placeholders.times} />
                    </Col>
                </FormGroup>
                <FormGroup row>
                    <Col sm={6}>
                        <Input type="textarea" name="fees" onChange={this.handleChange} id="fees" placeholder={this.placeholders.fees}/>
                    </Col>
                    <Col sm={6}>
                        <Input type="text" name="access" onChange={this.handleChange} id="access" placeholder={this.placeholders.access} />
                    </Col>
                </FormGroup>
                <hr/>
                {/* Location Information */}
                <FormGroup row> 
                    <Col sm={6}>
                        <Input type="text" name="gps" onChange={this.handleChange} id="gps" placeholder={this.placeholders.gps}/>
                    </Col>
                    <Col sm={6}>
                        <Input type="text" name="w3w" id="w3w" onChange={this.handleChange} placeholder={this.placeholders.w3w} />
                    </Col>
                </FormGroup>
                <hr/>
                <FormGroup row>
                    <Col sm={12}>
                        <Input type="textarea" name="txtHeader" onChange={this.handleChange} id="txtHeader" placeholder={this.placeholders.textHeader} />
                    </Col>
                </FormGroup>
                <FormGroup row>
                    <Col sm={12}>
                        <Input type="textarea" name="txtContent" onChange={this.handleChange} id="txtContent" placeholder={this.placeholders.textContent} />
                    </Col>
                </FormGroup>
                <FormGroup check row>
                    <Col sm={{ size: 10, offset: 2 }}>
                        <Button disabled={!this.validateForm()} type="submit" className="btn-primary">Tours &rarr;</Button>
                    </Col>
                </FormGroup> 
            </Form>
        );
    }
  }

  export default withRouter(NewFormDetails);

【问题讨论】:

  • 除了console.log 紧跟在setState 之后(这是不可以的,状态更新是异步,它似乎工作的原因是explained here) ,langEn/langEs 部分似乎很好。请使用 minimal reproducible example(注意“最小”)来说明问题,最好是使用 Stack Snippets([&lt;&gt;] 工具栏按钮)可运行的问题来更新您的问题。 Stack Snippets 支持 React,包括 JSX; here's how to do one.
  • 你设置this.state.language的组件(App)根本不使用this.state.language
  • 你没有在render()中使用this.state.languages
  • 旁注:用于获取data.endata.esif/else if 结构可以简化为:jsonLang = data[this.state.language] || data.en;
  • @T.J.Crowder 你是什么意思?应用程序不需要使用它,它只是在那里设置

标签: javascript reactjs


【解决方案1】:

问题是App 从不使用this.state.language,也没有将其传递给任何其他组件。

如果您希望将语言设置为App 级别,您至少有两种选择:

  1. 使用状态并将language 通过道具传递给需要它的组件(经典的lifting state up)。
  2. 使用context
  3. 我确定还有第三个选项使用hooks,但我还没有进入钩子。

#1 的快速示例:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            language: "en"
        };
        this.langEn = this.langEn.bind(this);
        this.langEs = this.langEs.bind(this);
    }
    langEs() {
        this.setState({language: "es"});
    }
    langEn() {
        this.setState({language: "en"});
    }
    render() {
        const {language} = this.state;
        return (
            <div>
                <button onClick={this.langEn}>English</button>
                <button onClick={this.langEs}>Español</button>
                <Thingy language={language} />
                <Whatsit language={language} />
            </div>
        );
    }
}

class Thingy extends React.Component {
    render() {
        const {language} = this.props;
        return <div>Thingy's language is {language}</div>;
    }
}

const Whatsit = props => (
  <div>Whatsit's language is {props.language}</div>
);

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<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>

#2 的快速示例:

const LangContext = React.createContext("en");

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            language: "en"
        };
        this.langEn = this.langEn.bind(this);
        this.langEs = this.langEs.bind(this);
    }
    langEs() {
        this.setState({language: "es"});
    }
    langEn() {
        this.setState({language: "en"});
    }
    render() {
        const {language} = this.state;
        return (
            <LangContext.Provider value={language}>
                <div>
                    <button onClick={this.langEn}>English</button>
                    <button onClick={this.langEs}>Español</button>
                    <Thingy />
                    <Whatsit />
                </div>
            </LangContext.Provider>
        );
    }
}

class Thingy extends React.Component {
    static contextType = LangContext;
    render() {
        const language = this.context;
        return <div>Thingy's language is {language}</div>;
    }
}

class Whatsit extends React.Component {
    static contextType = LangContext;
    render() {
        const language = this.context;
        return <div>Whatsit's language is {language}</div>;
    }
}

ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>

<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>

【讨论】:

  • 现在我需要努力让语言保持我交换页面时的样子哈哈
  • @TiernO - 为此,您可以将其存储在 localStorage 中,并从 App 的构造函数中的 localStorage 加载它。 More here.
【解决方案2】:

我注意到您从道具中获取语言并在 NewFormDetails 的构造函数中设置翻译后的字符串。当您单击按钮更改语言时,NewFormDetails 刚刚更新,无需执行构造函数,因此字符串是旧字符串。 我认为您可以从渲染函数中的道具获取语言,并在渲染函数中设置字符串。

【讨论】:

    【解决方案3】:

    正如 cmets 中已经提到的,您没有在 NewFormDetailsrender() 方法中使用 this.state.language。只有当新状态对 render() 方法产生实际影响时,React 才会重新渲染组件。

    由于您只是在构造函数中使用它,因此 react 不会重新渲染您的组件。只需将 if-else 语句移至 render 方法即可。

    编辑:

    您正在将 this.props.language 复制到您的组件状态,但之后您不会更改状态。 this.props.language 外部发生更改,但这些更改不会反映在组件内部。您应该直接在您的render() 方法中访问this.props.lanugage

    if (this.props.language == 'en') {
      ...
    }
    

    或者你有什么理由把它复制到州?

    【讨论】:

    • 我将下面的 let jsonLang; if (this.state.language == 'en') { jsonLang = data.en; } else if (this.state.language == 'es') { jsonLang = data.es; } else { jsonLang = data.en; } this.placeholders = jsonLang.placeholders; this.counties = jsonLang.counties; this.categories = jsonLang.categories; console.log(this.state.language) 移到了 render() 方法中,但没有任何区别。
    猜你喜欢
    • 2018-03-17
    • 1970-01-01
    • 2019-02-27
    • 2019-06-29
    • 2021-10-15
    • 2020-03-03
    • 2020-03-10
    相关资源
    最近更新 更多