Использование нескольких состояний `useState ()` вместе - PullRequest
0 голосов
/ 11 января 2020

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

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


Редактировать

Я звоню useEffect без параметров, чтобы он выполнялся при загрузке. Я ожидаю, что первый setTemplates() установит переменную состояния templates, чтобы я мог использовать ее при следующем вызове setTemplateChoices() и установить состояние templateChoices в одно и то же время.


// working version
import React, { useState, useEffect } from 'react'

export default function ParentComponent() {
  const [templates, setTemplates] = useState([])
  const [templateChoices, setTemplateChoices] = useState([])

  // this will actually be fetched from within the `useEffect()` call - 
  // I'm just putting it here to show structure and make it testable
  const defaultTemplates = [
    { id: 1, name: 'Form 1', },    // plus - groups: [ {}, {} ... ], etc.
    { id: 2, name: 'Form 2', },    // plus - groups: [ {}, {} ... ], etc.
    { id: 3, name: 'Form 3', },    // plus - groups: [ {}, {} ... ], etc.
  ]

  useEffect(() => {
    setTemplates(defaultTemplates)
  }, [])

  return (
    <div>
      <h2>Templates</h2>
      {templates.map(template => (
        <p>{template.name}</p>
      ))}
    </div>
  )
}

Но это не так:

// non-working version
import React, { useState, useEffect } from 'react'

export default function ParentComponent() {
  const [templates, setTemplates] = useState([])
  const [templateChoices, setTemplateChoices] = useState([])

  const defaultTemplates = [
    { id: 1, name: 'Form 1' },
    { id: 2, name: 'Form 2' },
    { id: 3, name: 'Form 3' },
  ]

  useEffect(() => {
    setTemplates(defaultTemplates)
    setTemplateChoices(templates.map(template => template.name))
  }, [])

  return (
    <div>
      <h2>Template Choices</h2>
      {templateChoices.map(choice => (
        <p>{choice}</p>
      ))}
      <p>Children go here depending on step</p>
    </div>
  )
}

Я пытался использовать asyn c и немедленное выполнение безрезультатно:

useEffect(() => {
  async function doWork() {
    await setTemplates(defaultTemplates)
    await setTemplateChoices(templates.map(template => template.name))
  }
  doWork()
}, [])

Я даже пытался отделиться их в два вызова useEffect:

useEffect(() => {
  setTemplates(defaultTemplates)
}, [])

useEffect(() => {
  setTemplateChoices(templates.map(template => template.name))
}, [templates])

Все еще нет радости. Почему не устанавливается templateChoices?

Ответы [ 4 ]

1 голос
/ 11 января 2020

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

Если нет, вы должны были непосредственно установить состояние (ie useState(defaultQuery) or useState(() => defaultQuery)). Это вызовет его только один раз при загрузке компонента.

Причина, по которой ваш первый образец не работает, заключается в том, что шаблон не обновляется немедленно. Требуется ссылка sh. Вы можете использовать useEffect дважды, но это неэффективно. Более простое решение будет

useEffect(() => {
    setTemplates(defaultTemplates);
    setTemplateChoices(defaultTemplates.map(template => template.name));
}, [defaultTemplates]);
0 голосов
/ 12 января 2020

Вот как я мог бы реализовать это с React, используя только 1 useEffect с двумя setState вызовами внутри него.

PS: Я не использовал asyn c потому что фрагмент не поддерживает его очень хорошо. Но вы можете использовать его.

function mockAPI() {

  const defaultTemplates = [
    { id: 1, name: 'Form 1' },
    { id: 2, name: 'Form 2' },
    { id: 3, name: 'Form 3' },
  ];

  return new Promise((resolve,reject) => {
    setTimeout(()=>resolve(defaultTemplates),1000);
  });
}

function App() {

  const [templates, setTemplates] = React.useState(null);
  const [templateChoices, setTemplateChoices] = React.useState([]);
  
  console.log("Rendering App...");
  console.log("templates: " + templates);
  console.log("templateChoices: " + templateChoices);

  React.useEffect(() => {
    console.log("Running useEffect...");
    if (!templates) {
      console.log("Fetching templates...");
      mockAPI().then((result) => setTemplates(result));
    }
    else {
      console.log("Setting templateChoices...");
      setTemplateChoices(templates.map(template => template.name));
    }
  },[templates]);

  const choiceItems = templateChoices.map((choice,index) => 
    <p key={index}>{choice}</p>
  );

  return(
    templates ?
      templateChoices ?
        <div>
          {choiceItems}
        </div>
      : null
    : <div>Loading templates...</div>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Лично я бы использовал один useEffect для извлечения шаблонов. И, если возможно, создайте все, что вам нужно, прямо из тела функции. Или используйте другой useEffect для этого.

Примерно так:

function mockAPI() {

  const defaultTemplates = [
    { id: 1, name: 'Form 1' },
    { id: 2, name: 'Form 2' },
    { id: 3, name: 'Form 3' },
  ];

  return new Promise((resolve,reject) => {
    setTimeout(()=>resolve(defaultTemplates),1000);
  });
}

function App() {

  const [templates, setTemplates] = React.useState(null);
  
  console.log("Rendering App...");
  console.log("templates: " + templates);

  React.useEffect(() => {
    console.log("Running useEffect...");
    console.log("Fetching templates...");
    mockAPI().then((result) => setTemplates(result));
  },[]);
  
  let choiceItems = null;
  
  if (templates) {
    console.log("Building choiceItems..");
    choiceItems = templates.map((template,index) => 
      <p key={index}>{template.name}</p>
    );
  }

  return(
    templates ?
      <div>
        {choiceItems}
      </div>
    : <div>Loading templates...</div>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
0 голосов
/ 12 января 2020

Просто для всех остальных разделение их на два вызова useEffect на самом деле работает:

useEffect(() => {
  setTemplates(defaultTemplates)
}, [])

useEffect(() => {
  setTemplateChoices(templates.map(template => template.name))
}, [templates])
0 голосов
/ 11 января 2020

установить значение по умолчанию для использованияState:

import React, { useState, useEffect } from 'react'

export default function ParentComponent() {
  const [templates, setTemplates] = useState([
    { id: 1, name: 'Form 1', },    // plus - groups: [ {}, {} ... ], etc.
    { id: 2, name: 'Form 2', },    // plus - groups: [ {}, {} ... ], etc.
    { id: 3, name: 'Form 3', },    // plus - groups: [ {}, {} ... ], etc.
  ])
  const [templateChoices, setTemplateChoices] = useState([])



  return (
    <div>
      <h2>Templates</h2>
      {templates.map(template => (
        <p>{template.name}</p>
      ))}
    </div>
  )
}
...