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:
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.