Hi,
Im experiencing issues with onEvent and onChange props for React Flowable Form component, onChange callback is not triggering at all and onEvent is not emitting “Payload.afterChange” event
when form field value is changed.
I am having simple and basic implementation inside my react project and using “@flowable/forms”: “3.16.0”,.
This is minimal implementation to reproduce:
TaskDetails.tsx
import { Form } from '@flowable/forms'
import {
saveTaskForm,
getTaskDetails,
getTaskForm,
getTaskVariables,
completeTaskForm,
} from '@/api/flowable/platform'
...rest of the imports
const TaskDetails = () => {
const navigate = useNavigate()
const { data: taskDetails, isPending: isTaskDetailsPending } = useQuery({
queryKey: ['taskDetails', taskId],
queryFn: async () => {
const detailsResponse = await getTaskDetails(taskId)
const variablesResponse = await getTaskVariables(taskId)
return { taskDetails: detailsResponse, variables: variablesResponse }
},
})
const { data: taskFormData, isPending: isTaskFormPending } = useQuery({
queryKey: ['taskForm', taskId],
queryFn: async () => {
const response = await getTaskForm(taskId)
return response
},
})
const { mutate: saveFormOnChange, isPending: isSaveFormOnChangePending } =
useMutation({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mutationFn: async (data: { event: string; conf: any, state: any, api: any }) => {
const fvalid = data.api.formValid();
if (fvalid && data.event === 'Component.blur') {
await saveTaskForm(taskId, data.api.payload.get())
await queryClient.invalidateQueries({ queryKey: ['taskDetails', taskId] })
await queryClient.invalidateQueries({ queryKey: ['taskForm', taskId] })
}
},
})
const { mutate: outcomeSubmit, isPending: isOutcomeSubmitPending } =
useMutation({
mutationFn: async (data: { result: string; payload: unknown }) => {
const response = await completeTaskForm(taskId, {
values: data.payload,
outcome: data.result,
})
navigate({ to: '/processes/$processInstanceId', params: { processInstanceId: taskDetails?.taskDetails.processInstanceId } })
return response
},
})
const { mutate: handleFormComplete, isPending: isHandleFormCompletePending } =
useMutation({
mutationFn: async () => {
const variables = await getTaskVariables(taskId)
const response = await completeTaskForm(taskId, {
values: variables,
outcome: 'complete',
})
navigate({ to: '/processes/$processInstanceId', params: { processInstanceId: taskDetails?.taskDetails.processInstanceId } })
return response
},
})
const { mutate: handleSaveForm, isPending: isSaveFormPending } =
useMutation({
mutationFn: async () => {
await saveTaskForm(taskId, taskDetails?.variables)
await queryClient.invalidateQueries({ queryKey: ['taskDetails', taskId] })
await queryClient.invalidateQueries({ queryKey: ['taskForm', taskId] })
},
})
if (isTaskFormPending || isTaskDetailsPending) {
return <div>Loading...</div>
}
return (
<div>
<h1>Task form</h1>
{/* Save task form button */}
{isSaveFormOnChangePending && <div>Saving...</div>}
{isOutcomeSubmitPending && <div>Outcome submit...</div>}
<Form
config={taskFormData}
onChange={(payload, changed) => {
console.log('onChange', { payload, changed })
}}
onOutcomePressed={(payload, result) => {
console.log('onOutcomePressed', { payload, result })
outcomeSubmit({ payload, result })
}}
onEvent={(event, conf, state, api) => {
console.log('onEvent', { event, conf, state, api })
saveFormOnChange({ event, conf, state, api })
return true;
}}
payload={taskDetails?.variables}
/>
{!taskFormData.outcomes && (
<div>
<button onClick={() => handleFormComplete()}>
{isHandleFormCompletePending ? 'Completing...' : 'Complete'}
</button>
</div>
)}
<div>
<button onClick={() => handleSaveForm()}>
{isSaveFormPending ? 'Saving...' : 'Save'}
</button>
</div>
</div>
)
}
/api/flowable/platform
import { platformApi } from '@/services/flowable/platformApi';
export const getTaskForm = async (taskId: string) => {
const response = await platformApi.get(`/tasks/${taskId}/form`);
return response.data;
};
export const getTaskDetails = async (taskId: string) => {
const response = await platformApi.get(`/tasks/${taskId}`);
return response.data;
};
export const getTaskVariables = async (taskId: string) => {
const response = await platformApi.get(`/tasks/${taskId}/variables`);
return response.data;
};
export const saveTaskForm = async (taskId: string, values: { [key: string]: unknown }) => {
const response = await platformApi.post(`/tasks/${taskId}/save-form`, values);
return response.data;
};
export const completeTaskForm = async (taskId: string, data: { values: unknown; outcome: string }) => {
const response = await platformApi.post(`/tasks/${taskId}/complete`, {
values: data.values,
outcome: data.outcome,
});
return response.data;
};
/services/flowable/platformApi
import axios, { AxiosError } from 'axios';
const platformApi = axios.create({
baseURL: "PLATFORM_API_BASE_URL",
headers: {
'Content-Type': 'application/json',
},
auth: {
username: 'admin',
password: 'test',
},
});
platformApi.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (error.response) {
return Promise.reject(error.response.data);
}
},
);
export { platformApi };
So as you can see, I have basic component TaskDetails.tsx that should only render task form and listen to changes, outcome pressed or if no outcomes, to provide complete form functionality. But I can’t manage to capture necessary events.
I tried other versions of flowable form component from 3.14.0 up until newest one 3.17.0 with no luck. Hopefully it is something on my end I am missing some basic stuff.
Also to mention, I am using flowable self-hosted app and playing around with your predefined processes inside and vitejs for bundling and dev server (“vite”: “^5.4.10”)