Home > database >  React update view after submitting form data using react-query in NextJS
React update view after submitting form data using react-query in NextJS

Time:12-30

I'm working on a NextJS project, and am running into an issue where my view isn't updating after a form submission. I'm using react-query to both fetch the data and do mutations.

I have a form called 'Add Day' which adds a day object to the database through an endpoint. But after making this update, my UI does not refresh to reflect this new object.

Here is a video that demonstrates it:

https://imgur.com/a/KIcQOfs

As you can see, when I refresh the page, the view updates. But I want it to update as soon as I submit the form and it is a success. Here's how my code is set up:

TripDetail

const TripDetailPage = () => {
    const router = useRouter()
    const queryClient = useQueryClient()

    const tripId = router.query.tripId as string

    const { data, isError, isLoading } = useQuery({
        queryKey: ['trips', tripId],
        queryFn: () => getTrip(tripId),
    })

    const [tabState, setTabState] = useState(1)

    const toggleTab = (index: number) => {
        setTabState(index)
    }

    const [modalVisible, setModalVisible] = useState<boolean>(false)

    const toggleModalVisible = () => {
        setModalVisible(!modalVisible)
    }

    const onDayAdded = () => {
        queryClient.invalidateQueries({ queryKey: ['trips', tripId] })
        console.log('refetching')
        console.log('day was added')
    }

    console.log('new data?')
    console.log(data)

    if (isLoading) {
        return <span>Loading...</span>
    }

    if (isError) {
        return <span>Error</span>
    }

    if (!data || !data.data || !data.data.name || !data.data.days) {
        return <span>Error</span>
    }

    return (
        <div className="ml-12 mr-12 mt-6">
            <div className="flex flex-row justify-between">
                <div className="order-first flex items-center">
                    <h1 className="text-2xl font-semibold text-slate-800">{data.data.name}</h1>
                    <Button className="btn btn-primary ml-8">Invite</Button>
                </div>
                <div className="order-last flex">
                    <Button className="btn btn-primary mr-4">Refresh</Button>
                    <Button className="btn btn-primary" onClick={toggleModalVisible}>
                        Add Day
                    </Button>
                </div>
                <AddDayModal
                    onSuccess={onDayAdded}
                    modalVisible={modalVisible}
                    toggleModalVisible={toggleModalVisible}
                    trip={data.data}
                />
            </div>
            <div id="tabs-nav" className="font-medium text-lg border-separate pb-2 border-b-2 border-gray-200 mt-6">
                <TabButton tabId={1} currentTabState={tabState} name="Plans" toggleTab={() => toggleTab(1)}></TabButton>
                <TabButton
                    tabId={2}
                    currentTabState={tabState}
                    name="Calendar"
                    toggleTab={() => toggleTab(2)}
                ></TabButton>
                <TabButton tabId={3} currentTabState={tabState} name="Ideas" toggleTab={() => toggleTab(3)}></TabButton>
            </div>
            <div id="content">
                {tabState === 1 && <PlannerBoard days={data.data.days} tripId={tripId}></PlannerBoard>}
                {tabState === 2 && <h1>Tab 2</h1>}
                {tabState === 3 && <h1>Tab 3</h1>}
            </div>
        </div>
    )
}

export default TripDetailPage

I'm calling invalidateQueries in onDayAdded which should refetch my data and update my view, but it doesn't seem to be. PlannerBoard is where I pass the data and is the component that renders the columns. It basically iterates through the days that is passed to it and renders them.

Planner Board:

type TDndPlannerProps = {
    days: TDay[]
    tripId: string
}

const PlannerBoard = (props: TDndPlannerProps) => {
    // ...

    props.days.forEach((day) => {
        // lots of processing, creates a data object which has the objects to make this view
    })

    const [state, setState] = useState<IData>(data) // after above processing

    // removed a bunch of code
    
    return (
        <div className="overflow-auto">
            <div>
                {day.map((day) => {
                    return <DayColumn key={day.id} day={day} />
                })}
            </div>
        </div>
    )
}

export default PlannerBoard

AddDay Modal:

type TModalProps = {
    trip: TTrip
    modalVisible: boolean
    toggleModalVisible: () => void
    onSuccess: () => void
}

const formArr: TFormField[] = [
    {
        label: 'Day name',
        name: 'name',
        type: 'text',
    },
    {
        label: 'Day date',
        name: 'date',
        type: 'date',
    },
]

const AddDayModal = (props: TModalProps) => {
    const [day, setDay] = useState<TDay>({
        name: '',
        tripId: props.trip.id!,
    })

    const onSuccess = () => {
        props.toggleModalVisible()
        props.onSuccess()
    }

    const { mutate, isLoading, isError, error } = useMutation((day: TDay) => postDay(day), { onSuccess })

    const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()

        mutate(day)
    }

    const onUpdate = (update: TFormFieldValue) => {
        setDay((prev) => {
            if (update.type == 'date') {
                return { ...prev, [update.name]: new Date(update.value) }
            } else {
                return { ...prev, [update.name]: update.value }
            }
        })
    }

    const formModalProps: TFormModalProps = {
        title: 'Add Day',
        formArr,
        submitBtn: 'Submit',
        submittingBtn: 'Adding',
        toggleVisible: props.toggleModalVisible,
        visible: props.modalVisible,
        onSubmit,
        isLoading,
        isError,
        error,
        onUpdate,
    }

    return (
        <div>
            <FormModal {...formModalProps} />
        </div>
    )
}

export default AddDayModal

The mutate here makes the axios call to my API which updates the db with this form data. Once we hit onSuccess the API call has succeeded and it call's TripDetailPage's onDayAdded().

Any idea how to make my UI update when the refetch completes? Do I need to use useState somehow?

CodePudding user response:

I believe this is the expected behaviour with react-query.

You can either use queryClient.invalidateQueries to refetch single or multiple queries in the cache or use queryClient.setQueryData to immediately update a query's cached data.

You can use this directly in the onSuccess callback if you wish.

See documentation.

CodePudding user response:

I believe I have a fix. In my PlannerBoard I needed to detect the change in props through useEffect and then manually set the state again as:

useEffect(() => {
    const data: IData = createBoardData(props.days)
    setState(data)
}, [props])

This forces the view to re-render.

  • Related