Refactor ActionHandler and EditObjectForm components to support forward refs and improve functionality

- Updated ActionHandler to use forwardRef, allowing parent components to access the callAction method.
- Enhanced EditObjectForm with forwardRef, enabling external control over editing state and form handling.
- Added onEdit and onStateChange props to EditObjectForm for better integration with parent components.
- Improved form validation and state management within EditObjectForm, ensuring a smoother user experience.
- Cleaned up code for better readability and maintainability.
This commit is contained in:
Tom Butcher 2025-08-18 00:58:52 +01:00
parent 4201f2b4a3
commit 678d5a0e90
2 changed files with 321 additions and 256 deletions

View File

@ -1,15 +1,19 @@
import React, { useEffect, useRef } from 'react' import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
const ActionHandler = ({ const ActionHandler = forwardRef(
(
{
children, children,
actions = {}, actions = {},
actionParam = 'action', actionParam = 'action',
clearAfterExecute = true, clearAfterExecute = true,
onAction, onAction,
loading = true loading = true
}) => { },
ref
) => {
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const action = new URLSearchParams(location.search).get(actionParam) const action = new URLSearchParams(location.search).get(actionParam)
@ -36,6 +40,7 @@ const ActionHandler = ({
) { ) {
// Execute the action // Execute the action
const result = actions[action]() const result = actions[action]()
// Mark this action as executed // Mark this action as executed
lastExecutedAction.current = action lastExecutedAction.current = action
// Call optional callback // Call optional callback
@ -66,9 +71,16 @@ const ActionHandler = ({
navigate navigate
]) ])
useImperativeHandle(ref, () => ({
callAction
}))
// Return null as this is a utility component // Return null as this is a utility component
return <>{children({ callAction })}</> return children
} }
)
ActionHandler.displayName = 'ActionHandler'
ActionHandler.propTypes = { ActionHandler.propTypes = {
children: PropTypes.func, children: PropTypes.func,

View File

@ -1,4 +1,11 @@
import React, { useState, useEffect, useContext, useCallback } from 'react' import React, {
useState,
useEffect,
useContext,
useCallback,
forwardRef,
useImperativeHandle
} from 'react'
import { Form, message } from 'antd' import { Form, message } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext' import { ApiServerContext } from '../context/ApiServerContext'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
@ -18,7 +25,8 @@ import merge from 'lodash/merge'
* loading, isEditing, startEditing, cancelEditing, handleUpdate, form, formValid, objectData, setIsEditing, setObjectData * loading, isEditing, startEditing, cancelEditing, handleUpdate, form, formValid, objectData, setIsEditing, setObjectData
* }) => ReactNode * }) => ReactNode
*/ */
const EditObjectForm = ({ id, type, style, children }) => { const EditObjectForm = forwardRef(
({ id, type, style, children, onEdit, onStateChange }, ref) => {
const [objectData, setObjectData] = useState(null) const [objectData, setObjectData] = useState(null)
const [serverObjectData, setServerObjectData] = useState(null) const [serverObjectData, setServerObjectData] = useState(null)
const [fetchLoading, setFetchLoading] = useState(true) const [fetchLoading, setFetchLoading] = useState(true)
@ -41,16 +49,22 @@ const EditObjectForm = ({ id, type, style, children }) => {
fetchObjectLock, fetchObjectLock,
showError, showError,
connected, connected,
subscribeToObject, subscribeToObjectUpdates,
subscribeToLock subscribeToObjectLock
} = useContext(ApiServerContext) } = useContext(ApiServerContext)
const { token } = useContext(AuthContext) const { token } = useContext(AuthContext)
// Validate form on change // Validate form on change
useEffect(() => { useEffect(() => {
form form
.validateFields({ validateOnly: true }) .validateFields({ validateOnly: true })
.then(() => setFormValid(true)) .then(() => {
.catch(() => setFormValid(false)) setFormValid(true)
onStateChange({ formValid: true })
})
.catch(() => {
setFormValid(false)
onStateChange({ formValid: true })
})
}, [form, formUpdateValues]) }, [form, formUpdateValues])
// Cleanup on unmount // Cleanup on unmount
@ -65,13 +79,16 @@ const EditObjectForm = ({ id, type, style, children }) => {
const handleFetchObject = useCallback(async () => { const handleFetchObject = useCallback(async () => {
try { try {
setFetchLoading(true) setFetchLoading(true)
onStateChange({ loading: true })
const data = await fetchObject(id, type) const data = await fetchObject(id, type)
const lockEvent = await fetchObjectLock(id, type) const lockEvent = await fetchObjectLock(id, type)
setLock(lockEvent) setLock(lockEvent)
onStateChange({ lock: lockEvent })
setObjectData(data) setObjectData(data)
setServerObjectData(data) setServerObjectData(data)
form.setFieldsValue(data) form.setFieldsValue(data)
setFetchLoading(false) setFetchLoading(false)
onStateChange({ loading: false })
} catch (err) { } catch (err) {
messageApi.error('Failed to fetch object info') messageApi.error('Failed to fetch object info')
showError( showError(
@ -88,7 +105,10 @@ const EditObjectForm = ({ id, type, style, children }) => {
// Update event handler // Update event handler
const updateLockEventHandler = useCallback((value) => { const updateLockEventHandler = useCallback((value) => {
setLock((prev) => ({ ...prev, ...value })) setLock((prev) => {
onStateChange({ lock: { ...prev, ...value } })
return { ...prev, ...value }
})
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -100,22 +120,26 @@ const EditObjectForm = ({ id, type, style, children }) => {
useEffect(() => { useEffect(() => {
if (id && connected) { if (id && connected) {
const objectUnsubscribe = subscribeToObject( const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
id, id,
type, type,
updateObjectEventHandler updateObjectEventHandler
) )
const lockUnsubscribe = subscribeToLock(id, type, updateLockEventHandler) const lockUnsubscribe = subscribeToObjectLock(
id,
type,
updateLockEventHandler
)
return () => { return () => {
if (objectUnsubscribe) objectUnsubscribe() if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
if (lockUnsubscribe) lockUnsubscribe() if (lockUnsubscribe) lockUnsubscribe()
} }
} }
}, [ }, [
id, id,
type, type,
subscribeToObject, subscribeToObjectUpdates,
subscribeToLock, subscribeToObjectLock,
updateObjectEventHandler, updateObjectEventHandler,
connected, connected,
updateLockEventHandler updateLockEventHandler
@ -123,6 +147,7 @@ const EditObjectForm = ({ id, type, style, children }) => {
const startEditing = () => { const startEditing = () => {
setIsEditing(true) setIsEditing(true)
onStateChange({ isEditing: true })
lockObject(id, type) lockObject(id, type)
} }
@ -132,6 +157,7 @@ const EditObjectForm = ({ id, type, style, children }) => {
setObjectData(serverObjectData) setObjectData(serverObjectData)
} }
setIsEditing(false) setIsEditing(false)
onStateChange({ isEditing: false })
unlockObject(id, type) unlockObject(id, type)
} }
@ -139,9 +165,11 @@ const EditObjectForm = ({ id, type, style, children }) => {
try { try {
const value = await form.validateFields() const value = await form.validateFields()
setEditLoading(true) setEditLoading(true)
onStateChange({ editLoading: true })
await updateObject(id, type, value) await updateObject(id, type, value)
setObjectData({ ...objectData, ...value }) setObjectData({ ...objectData, ...value })
setIsEditing(false) setIsEditing(false)
onStateChange({ isEditing: false })
messageApi.success('Information updated successfully') messageApi.success('Information updated successfully')
} catch (err) { } catch (err) {
if (err.errorFields) { if (err.errorFields) {
@ -155,6 +183,7 @@ const EditObjectForm = ({ id, type, style, children }) => {
} finally { } finally {
handleFetchObject() handleFetchObject()
setEditLoading(false) setEditLoading(false)
onStateChange({ editLoading: false })
} }
} }
@ -180,6 +209,20 @@ const EditObjectForm = ({ id, type, style, children }) => {
} }
} }
useImperativeHandle(ref, () => ({
startEditing,
cancelEditing,
handleUpdate,
handleDelete,
confirmDelete,
handleFetchObject,
editLoading,
fetchLoading,
isEditing,
objectData,
lock
}))
return ( return (
<> <>
<DeleteObjectModal <DeleteObjectModal
@ -195,7 +238,12 @@ const EditObjectForm = ({ id, type, style, children }) => {
layout='vertical' layout='vertical'
style={style} style={style}
onValuesChange={(values) => { onValuesChange={(values) => {
setObjectData((prev) => ({ ...prev, ...values })) if (onEdit != undefined) {
onEdit(values)
}
setObjectData((prev) => {
return { ...prev, ...values }
})
}} }}
> >
{contextHolder} {contextHolder}
@ -218,13 +266,18 @@ const EditObjectForm = ({ id, type, style, children }) => {
</Form> </Form>
</> </>
) )
} }
)
EditObjectForm.displayName = 'EditObjectForm'
EditObjectForm.propTypes = { EditObjectForm.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
children: PropTypes.func.isRequired, children: PropTypes.func.isRequired,
style: PropTypes.object style: PropTypes.object,
onEdit: PropTypes.func,
onStateChange: PropTypes.func
} }
export default EditObjectForm export default EditObjectForm