Tengo un formulario con varias capas de componentes secundarios. El estado del formulario se mantiene al más alto nivel y paso funciones como accesorios para actualizar el nivel superior. El único problema con esto es cuando el formulario se hace muy grande (puede agregar preguntas dinámicamente) cada componente se recarga cuando uno de ellos se actualiza. Aquí hay una versión simplificada de mi código (o el codesandbox: https://codesandbox.io/s/636xwz3rr):
const App = () => {
return <Form />;
}
const initialForm = {
id: 1,
sections: [
{
ordinal: 1,
name: "Section Number One",
questions: [
{ ordinal: 1, text: "Who?", response: "" },
{ ordinal: 2, text: "What?", response: "" },
{ ordinal: 3, text: "Where?", response: "" }
]
},
{
ordinal: 2,
name: "Numero dos",
questions: [
{ ordinal: 1, text: "Who?", response: "" },
{ ordinal: 2, text: "What?", response: "" },
{ ordinal: 3, text: "Where?", response: "" }
]
}
]
};
const Form = () => {
const [form, setForm] = useState(initialForm);
const updateSection = (idx, value) => {
const { sections } = form;
sections[idx] = value;
setForm({ ...form, sections });
};
return (
<>
{form.sections.map((section, idx) => (
<Section
key={section.ordinal}
section={section}
updateSection={value => updateSection(idx, value)}
/>
))}
</>
);
};
const Section = props => {
const { section, updateSection } = props;
const updateQuestion = (idx, value) => {
const { questions } = section;
questions[idx] = value;
updateSection({ ...section, questions });
};
console.log(`Rendered section "${section.name}"`);
return (
<>
<div style={{ fontSize: 18, fontWeight: "bold", margin: "24px 0" }}>
Section name:
<input
type="text"
value={section.name}
onChange={e => updateSection({ ...section, name: e.target.value })}
/>
</div>
<div style={{ marginLeft: 36 }}>
{section.questions.map((question, idx) => (
<Question
key={question.ordinal}
question={question}
updateQuestion={v => updateQuestion(idx, v)}
/>
))}
</div>
</>
);
};
const Question = props => {
const { question, updateQuestion } = props;
console.log(`Rendered question #${question.ordinal}`);
return (
<>
<div>{question.text}</div>
<input
type="text"
value={question.response}
onChange={e =>
updateQuestion({ ...question, response: e.target.value })
}
/>
</>
);
};
Intenté usar useMemo y useCallback, pero no puedo entender cómo hacerlo funcionar. El problema es transmitir la función para actualizar su padre. No puedo entender cómo hacerlo sin actualizarlo cada vez que se actualiza el formulario.
No puedo encontrar una solución en línea en ningún lado. Tal vez estoy buscando lo incorrecto. ¡Gracias por cualquier ayuda que pueda ofrecer!
Solución
Usando la respuesta de Andrii-Golubenko y este artículo Reaccione optimizaciones con React.memo, useCallback y useReducer pude encontrar esta solución: https://codesandbox.io/s/myrjqrjm18
Observe cómo el registro de la consola solo muestra la representación de los componentes que han cambiado.
3 respuestas
- Use la función React
React.memo
para componentes funcionales para evitar volver a renderizar si los accesorios no cambian, de manera similar a PureComponent para componentes de clase. - Cuando pasa la devolución de llamada de esa manera:
<Section
...
updateSection={value => updateSection(idx, value)}
/>
Su componente Section
se volverá a mostrar cada vez que se vuelva a generar el componente principal, incluso si no se cambian otros accesorios y se usa React.memo
. Porque su devolución de llamada se volverá a crear cada vez que se represente el componente principal. Debe envolver su devolución de llamada en gancho useCallback
.
- Usar
useState
no es una buena decisión si necesita almacenar objetos complejos comoinitialForm
. Es mejor usaruseReducer
;
Aquí puede ver la solución de trabajo: https://codesandbox.io/s/o10p05m2vz
Sugeriría usar métodos de ciclo de vida para evitar la reproducción, en el ejemplo de ganchos de reacción que puede usar, useEffect. También centralizar su estado en contexto y usar el enlace useContext probablemente también ayudaría.
Trabajando a través de este problema con un formulario complejo, el truco que implementé fue utilizar onBlur={updateFormState}
en los elementos de entrada del componente para activar la elevación de los datos del formulario al componente principal a través de una función pasada como accesorio al componente.
Para actualizar el elemento de entrada del componente, usé onChange={handleInput}
usando un estado dentro del componente, qué estado del componente se pasó a la función de elevación cuando la entrada (o el componente en su conjunto, si hay múltiples campos de entrada en el componente ) perdió el enfoque.
Esto es un poco hacky, y probablemente esté mal por alguna razón, pero funciona en mi máquina. ;-)
Preguntas relacionadas
Preguntas vinculadas
Nuevas preguntas
javascript
Para preguntas sobre la programación en ECMAScript (JavaScript / JS) y sus diversos dialectos / implementaciones (excepto ActionScript). Incluya todas las etiquetas relevantes en su pregunta; por ejemplo, [node.js], [jquery], [json], etc.