Quiero que los elementos secundarios actualicen el estado de mi componente principal de forma independiente, sin que el elemento principal realice una microgestión y esté al tanto de las variables modificadas por sus elementos secundarios. Estoy resumiendo estas modificaciones con un solo método
onIssueDataChanged
. El objeto destate
modificado luego se transferirá a GraphQL, por lo que no es necesario que el padre sepa lo que contiene. Mi problema es que los niños no pueden llamar aonIssueDataChanged
enuseEffect
.
Aquí está el problema concreto:
Tengo un formulario IssueFieldsForm
que maneja el guardado de un objeto llamado issue
.
El objeto tiene una galería de imágenes incluida, las imágenes se pueden agregar, eliminar o seleccionar como la imagen principal del problema. Esa lógica está aislada en un componente separado llamado GalleryComp
y la información se almacena en un campo de problema no persistente llamado attachmentsMeta
.
Mi problema es que quiero aislar el manejo de los attachmentsMeta
en GalleryComp
y tener en IssueFieldsForm
un método genérico al que llamé onIssueDataChanged
que solo toma valores modificados y actualiza el estado de la variable de issue
con él. Eso parece difícil de hacer ya que inicializo un estado de attachmentsMeta
en GalleryComp
con los attachments
actuales en el useEffect
, pero no puedo enviar eso de vuelta al objeto de estado de issue
en el padre.
export default function GalleryComp(props) { const {issue, onItemsChanged} = props const [attachmentsMeta, setAttachmentsMeta] = useState({ images: [], attachmentsAttributes: {}, mainAttachmentIndex: 0 }) useEffect(() => { const newAttachmentsMeta = {...attachmentsMeta} newAttachmentsMeta.images = [] logger.debug('attachments cont before concat: ', newAttachmentsMeta.images.length) newAttachmentsMeta.images = unionBy(issue.attachments, issue.attachmentsMeta?.images, 'url') logger.debug('after concat: ', newAttachmentsMeta.images.length) newAttachmentsMeta.mainAttachmentIndex = issue.mainAttachmentIndex logger.debug('displaying attachments: ', newAttachmentsMeta) setAttachmentsMeta(newAttachmentsMeta) }, [issue?.attachmentsMeta])
Eso es un problema para mí porque si no hago ningún cambio en los archivos adjuntos, nunca se llamará a onIssueDataChanged
para actualizar la variable issue.attachmentsMeta
con los attachments
iniciales. Lo que me llevó a perder mis archivos adjuntos originales al guardarlos (ya que no se transfirieron a mi única fuente de verdad, problema. issue.attachmentsMeta
).
Por otro lado, si inicializo issue.attachmentsMeta
en el componente principal, pierdo la elegancia de que cada componente maneje sus propias variables.
pd: Obviamente, no puedo llamar a onIssueDataChanged
en el efecto, ya que desencadenaría un cambio de estado y entraría en un bucle infinito.
¿Cómo harías esto?
Resolví el problema exportando una función initGalleryData
de GalleryComp
y usándola en el formulario. De esta manera, los detalles se ocultan en el niño.
De esta manera, puedo reutilizar el Componente de la Galería con cualquier objeto, simplemente sin olvidar iniciar el objeto y proporcionando un modificador 'onDataChange'
Este código se usa en el proyecto weally.org , que reúne a personas para protestas en línea por concens
export default function GalleryComp(props) { const {issue, onItemsChanged} = props const attachmentsMeta = issue.attachmentsMeta const {t} = useTranslation('complaintList') function setMainAttachmentIndex(index) { const attachmentsMetaCopy = {...attachmentsMeta} attachmentsMetaCopy.mainAttachmentIndex = index onItemsChanged({attachmentsMeta:attachmentsMetaCopy}) } function onAttachmentRemoveAction(attachmentId, removed) { const attachmentsModified = {...attachmentsMeta} let attr = attachmentsModified.attachmentsAttributes[attachmentId] if (!attr) { attr = {deleted: removed} attachmentsModified.attachmentsAttributes[attachmentId] = attr } else { attr.deleted = removed } onItemsChanged({attachmentsMeta: attachmentsModified}) } if (attachmentsMeta.images?.length === 0) return <Divider style={sx.descDivider}/> return ( <> <Gallery attachments={attachmentsMeta} onRemoveAction={onAttachmentRemoveAction} selectedIndex={attachmentsMeta.mainAttachmentIndex} onSelectionChange={setMainAttachmentIndex}/> <Divider style={sx.descDivider}/> </> ) } export function initGalleryData(issue) { issue.attachmentsMeta = { images: [...issue.attachments], attachmentsAttributes: {}, mainAttachmentIndex: 0 } }
Luego solo tengo que inicializar los campos necesarios en la creación del componente.
export default function IssueFieldsForm(props) { const {t} = useTranslation('complaintList') const { issue, onIssueDataChanged, markers, onMarkersChanged, onMarkerSelected, setPlaceMarkersDisplayed, placeMarkersDisplayed } = props initGalleryData(issue); const [validationState, setValidationState] = useState(new ValidationResult()) //... return ( <Card sx={sx.issueFieldsForm} id="IssueFieldsForm"> <Grid item sx={sx.item} ref={scrollTopRef}> <Typography variant={'h5'} sx={sx.header}>{t('edit.header')}</Typography> </Grid> //... <GalleryComp issue={issue} onItemsChanged={onIssueDataChanged}/> //... )