Убедитесь, что родительский элемент отображается только тогда, когда все дочерние элементы загружены

Я очень новичок в реакции, и я борюсь со следующим. Я создал форму в React, и эта форма содержит раскрывающийся список. Мне нужно повторно использовать это раскрывающееся меню на нескольких страницах, поэтому я решил сделать его компонентом, который полностью отвечает за получение всех данных.

В компоненте формы я получаю все данные, и одно из этих полей - selectedServerDataId. Поле selectId содержит идентификатор значения, которое необходимо выбрать в раскрывающемся списке.

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

В настоящее время, когда страница отображается, я сначала вижу идентификатор выбранного значения в раскрывающемся списке, а через долю секунды я вижу фактическую метку выбранного значения.

Есть ли способ гарантировать, что родительский компонент отображается только тогда, когда дочерние элементы полностью загружены?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
51
2

Ответы 2

Основная проблема здесь заключается в том, что у вас есть два компонента, которые имеют строгие иерархические отношения рендеринга (родительский -> дочерний), но равную ответственность за выборку данных.

Это приводит к ситуации, когда вы должны реагировать на получение данных между несколькими компонентами в определенном порядке, что является нетривиальной проблемой параллелизма. Одним из решений было бы использовать контейнер общего состояния (с использованием Redux или Контекстный 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>
  );
}

Спасибо за развернутый ответ. Ваш ответ наводит меня на мысль, что я стараюсь реагировать, а не реагировать. Как бэкэнд-программист я всегда стараюсь создавать компоненты, которые можно использовать повторно и за которые я несу ответственность. Я посмотрю на предоставленные библиотеки, но в противном случае я пойду с родительской загрузкой всего маршрута. Хотя я надеюсь, что найду способ справиться с этим, потому что это могло бы сэкономить мне много кода внешнего интерфейса в приложении, которое я делаю.

Patrick 01.07.2018 00:28

Рад помочь товарищу. Компоненты в React должны быть многоразовыми, ваша идея верна. Наличие чего-либо с одной ответственностью - это твердый принцип разработки программного обеспечения, независимо от технологической части (серверная часть, интерфейсная часть и т. д.). Я полностью согласен с вами по всем этим пунктам. Единственная проблема в вашем подходе - это то, где вы нарисовали границу компонента. Мое предложение здесь основано на небольшом образце кода, который вы дали, но лучший совет, который я получил, - это взять ваше представление о повторном использовании и единоличной ответственности и немного покачать дизайн вашего компонента :)

nem035 01.07.2018 01:10

Это действительно небольшой фрагмент кода. Компонент данных будет представлять собой раскрывающийся список, рядом с которым будет находиться кнопка добавления, которая откроет окно модели для добавления нового элемента в раскрывающийся список. В настоящее время работаю над идеей, которая, кажется, работает, но еще не реализована;) Я также поделюсь этой частью кода.

Patrick 01.07.2018 01:29

После многих попыток и ошибок я нашел способ исправить это.

В моем родительском состоянии я добавил следующий объект:

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.info(loadedComponents);
    for (var o in loadedComponents) {
        if (!loadedComponents[o]) return false;
    }
    return true;
}

Изменил метод рендеринга на это:

        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, но он работает

Другие вопросы по теме