确保仅在加载所有子项时显示父项

时间:2018-06-30 16:27:34

标签: javascript reactjs

我是一个非常新的反应者,我正在努力解决以下问题。我在react中创建了一个表单,该表单包含一个下拉列表。我需要在多个页面中重复使用该下拉菜单,所以我认为将其做成完全负责获取所有数据的组件。

在表单组件中,我获取了所有数据,并且这些字段之一是selectedServerDataId。 selectId字段包含需要在下拉列表中选择的值的ID。

<snip includes/>

class Arrival extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            formData: {}
        };
    }

   async componentDidMount() {
        await fetch(urls.addEditUrl)
            .then(response => response.json())
            .then(data => {
                this.setState({ formData: data });
            });
    }

    <snip some setstate helpers/>

    render() {
        const { formData } = this.state;
        return (
                <Form >
                <Selector label="select"
                    onChange={this.UpdateFormSelectData.bind(this, 'selectedServerDataId')}
                    value={formData.selectedServerDataId}/>
                <DateItem label="date"
                          allowClear={true}
                          value={formData.date}
                          onChange={this.updateFormDateData.bind(this, 'date')}/>
                <snip more fields.../>
            </Form>);
    }
}

export default Arrival;

父组件中的访存将检索编辑表单的数据,其中包括selectedServerDataId。我的子组件如下所示:

<snip usings />

class ServerDataSelector extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            options: [],
        };
    }

    async componentDidMount() {
        await fetch(URLS.GetServerData)
            .then(response => response.json())
            .then(data => {
                this.setState({ options: data });
            });
    }

    render() {
        const { options } = this.state;
        return (
            <FormItem {...formItemLayout} label={t(this.props.label)} >
                <Select setValue={this.props.onChange} getValue={() => this.props.value} selectedOption={this.props.value} name={this.props.label}>
                        {options.map(d => <Option key={d.id} value={d.id}>{d.value}</Option>)}
                </Select>
                  //TODO add model window to manipulate list.
                </FormItem>);
    }
}

export default ServerDataSelector 
ServerDataSelector { Selector }

当前,当页面呈现时,我首先在下拉列表中看到所选值的ID,然后一秒钟之后,我看到了实际的所选值标签。

是否有一种方法可以确保仅在子项完全完成加载时才渲染父组件?

2 个答案:

答案 0 :(得分:0)

这里的主要问题是,您有两个组件具有严格的呈现层次关系(父级->子级),但具有相同的数据提取责任。

这导致您必须对多个组件之间以特定顺序进行的数据检索作出反应,这是一个非同寻常的并发问题。一种解决方案是使用共享状态容器(使用ReduxContext API),并使两个组件将其结果保存到此共享状态存储桶中,然后每当存储桶中包含所有数据时,都将其呈现(例如,直到您可以显示微调框)。

一种更简单的解决方案是将获取操作移至一个组件中,完全消除这种拆分的数据获取责任,并允许组件获取数据也控制渲染。

一种方法是获取父级中的所有数据,并使子级成为接收数据时呈现的纯组件:

class Arrival extends React.Component {
  // ...

  async componentDidMount() {
    const [formData, options] = await Promise.all([
      fetch(urls.addEditUrl).then(response => response.json()),
      fetch(URLS.GetServerData).then(response => response.json())
    ]);

    this.setState({ formData, options });
  }

  render() {
    const { formData, options } = this.state;
    return (
      <Form>
        {/* ... */}
        {formData &&
          options && (
            <Selector
              label="select"
              options={this.state.options}  {/* pass the options here */}
              onChange={this.UpdateFormSelectData.bind(
                this,
                "selectedServerDataId"
              )}
              value={formData.selectedServerDataId}
            />
          )}
      </Form>
    );
  }
}

Selector现在只是表示性组件,不需要状态:

const ServerDataSelector = props => (
  <FormItem {...formItemLayout} label={t(this.props.label)}>
    <Select
      setValue={this.props.onChange}
      getValue={() => this.props.value}
      selectedOption={this.props.value}
      name={this.props.label}
    >
      {this.props.options.map(d => (     {/* use props.options */}
        <Option key={d.id} value={d.id}>
          {d.value}
        </Option>
      ))}
    </Select>
  </FormItem>
);

您可以选择显示微调器或加载指示器:

...
{
  formData && options ? (
    <Selector
      label="select"
      onChange={this.UpdateFormSelectData.bind(this, "selectedServerDataId")}
      options={this.state.options}
      value={formData.selectedServerDataId}
    />
  ) : (
    <Spinner />
  );
}

或者,您可以让子级获取所有数据,而父级将仅呈现该子级。

class ServerDataSelector extends React.Component {
  // ...

  async componentDidMount() {
    const [formData, options] = await Promise.all([
      fetch(urls.addEditUrl).then(response => response.json()),
      fetch(URLS.GetServerData).then(response => response.json())
    ]);

    this.setState({
      value: formData.selectedServerDataId,
      options
    });
  }

  render() {
    return (
      <FormItem {...formItemLayout} label={t(this.props.label)}>
        <Select
          setValue={this.props.onChange}
          getValue={() => this.state.value}  {/* use the value from the state */}
          selectedOption={this.state.value}  {/* use the value from the state */}
          name={this.props.label}
        >
          {this.state.options.map(d => (
            <Option key={d.id} value={d.id}>
              {d.value}
            </Option>
          ))}
        </Select>
      </FormItem>
    );
  }
}

您还可以在子级中添加一个微调框,以防止页面出现混乱并允许使用更好的UX:

render() {
  if (!this.state.value || !this.state.options) {
    return <Spinner />;
  }

  return (
    <FormItem {...formItemLayout} label={t(this.props.label)}>
      <Select
        setValue={this.props.onChange}
        getValue={() => this.state.value}
        selectedOption={this.state.value}
        name={this.props.label}
      >
        {this.state.options.map(d => (
          <Option key={d.id} value={d.id}>
            {d.value}
          </Option>
        ))}
      </Select>
    </FormItem>
  );
}

答案 1 :(得分:0)

经过多次尝试和错误,我找到了一种解决方法。

在我的父状态下,添加了以下对象:

this.state = {
            formData: null,
            loadedComponents: {
                parentComponentIsLoaded: false,
                childComponent1IsLoaded: false,
                childComponent2IsLoaded: false,
            }
        };

然后我在父母中添加了以下2种方法

   componentLoaded = (field, data) => {
    const { loadedComponents } = this.state;
    var fieldName = field + "IsLoaded";

    this.setState({ loadedComponents: { ...loadedComponents, [fieldName]: true } });
}

allComponentsLoaded() {

    const { loadedComponents } = this.state;
    console.log(loadedComponents);
    for (var o in loadedComponents) {
        if (!loadedComponents[o]) return false;
    }
    return true;
}

将render方法更改为此:

        return (
        formData ?
            <Form className={this.allComponentsLoaded() ? '' : 'hidden'} >
                <ChildComponet1 {someprops} isLoaded={this.componentLoaded}/>
                <ChildComponent2 {someprops} isLoaded={this.componentLoaded}/>
            <snip />
        </Form> : <div />);

并在获取数据后在childs componentDidMount方法中添加以下行。

this.props.isLoaded('childComponet1', true)

也许这是非常糟糕的反应设计,您是否应该实际使用MEM035的答案,但这确实有用