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 destatemodificado 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 aonIssueDataChangedenuseEffect.
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}/> //... )