Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

237
Views
How to correctly isolate states in React

I want my parent component state to be updated by its children independently, without the parent doing micromanagement and being aware of variables modified by its children. I'm abstracting these modifications with a single method onIssueDataChanged. The modified state object will then be transferred to GraphQL, so no need for the parent to know what's in it. My problem, is that children cannot call onIssueDataChanged in useEffect.

Here's the concrete problem:

I have a form IssueFieldsForm that handles the saving of an object called issue.

The object has a gallery of pictures included, the pictures can be added, removed or selected as the main issue picture. That logic is isolated in a separate component called GalleryComp, and the info is stored in a non-persistent issue field called attachmentsMeta.

My problem is that I want to isolate handling of the attachmentsMeta in GalleryComp and have in IssueFieldsForm a generic method I called onIssueDataChanged which takes only changed values and updates the issue variable state with it. That seems hard to do since I initialize an attachmentsMeta state in GalleryComp with the current attachments in the useEffect hook, but I cannot send that back to the issue state object in the parent.

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])

That is a problem for me because if I don't do any change in the attachments, onIssueDataChanged will never be called to update the variable issue.attachmentsMeta with the initial attachments. Leading me to lose my original attachments on save (since they were not transferred to my single source of truth issue.attachmentsMeta).

On the other hand, if I initialize issue.attachmentsMeta in the parent component, I lose the elegance of having each component handle its own variables.

p.s: Obviously I cannot call onIssueDataChanged in the effect since it would trigger a state change and enter an infinite loop.

How would you do this?

about 4 years ago · Juan Pablo Isaza
1 answers
Answer question

0

I solved the issue by exporting an initGalleryData function from GalleryComp and using it in the form. This way, details are hidden in the child.

This way, I can reuse the Gallery Component with any object, by simply not forgetting to initiate the object and by providing an 'onDataChange' modificator

This code is used in the weally.org project, gathering people for protests online by 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
    }
}

I then just have to initialize the needed fields at component creation.

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}/>
//...
)
about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!