I'm trying to create a group
field with a set of sliders to control numerical values and a ui
field do display the result of these values in a chart that updates dynamically as you change the group values.
My Group Field looks something like this...
const MyChartField: GroupField = {
name: 'my-chart-field',
type: 'group',
required: true,
fields: [
{
name: 'Preview',
type: 'ui',
admin: {
components: {
Cell: (args) => {
console.log('cell', args)
return <div>Cell</div>
},
Field: (args) => {
// Uncommenting the following hooks causes infinite re-renders
// Get the `x` field state.
// const x = useFormFields(
// ([fields, dispatch]) => fields['my-chart-field.x']
// )
// Get the `y` field state.
// const y = useFormFields(
// ([fields, dispatch]) => fields['my-chart-field.y']
// )
console.log('field', args)
return <div>Field</div>
},
},
},
},
Slider({ name: 'x' }),
Slider({ name: 'y' }),
],
}
My issue is that whenever I try to access sibling data from within the admin.components.Field
of a ui
field config my UI component continuously rerenders. What am I doing wrong? Or is there some other way to achieve something similar?
Thanks in advance!
UPDATE:
Turned out to be my custom Slider
component used in conjunction with my custom UI component. Still investigating why. When the Slider
fields are replaced with simple number
fields, everything works as expected.
Hey @asibilia can you throw this into another file and then import that component here? I am unsure about using react right in the config file itself. Let me know if you get around to giving that a shot!
Unfortunately, that doesn't seem to change anything. I actually had the Field
component abstracted out to a separate file already but inlined it in the code snippet for clarity.
// my-chart-group-field.ts
const MyChartField: GroupField = {
name: 'my-chart-field',
type: 'group',
required: true,
fields: [
{
name: 'Preview',
type: 'ui',
admin: {
components: {
Cell: (args) => {
console.log('cell', args)
return <div>Cell</div>
},
Field: MyChartUIField,
},
},
},
Slider({ name: 'x' }),
Slider({ name: 'y' }),
],
}
// my-chart-ui-field.tsx
const MyChartUIField: FunctionComponent<{ path: string }> = ({ path }) => {
// Uncommenting the following hooks causes infinite re-renders
// Get the `x` field state.
// const x = useFormFields(
// ([fields, dispatch]) => fields.x
// )
// Get the `y` field state.
// const y = useFormFields(
// ([fields, dispatch]) => fields.y
// )
console.log('field', args)
return <div>Field</div>
}
export default React.memo(ProjectilePreview, isEqual)
I've also memoized the child Field
component just in case it was caused by the props changing (which doesn't have any effect)
I am curious what the Slider field is doing, does that have a custom component? I just threw an example together just like yours and I am not seeing infinite re-renders.
Okay, so i think you're onto something... If i replace my custom slider components with just a simple number input I don't get the re-rendering when using useFormFields
in the UI field.
Here's how I have my slider component set up, I must be doing something wrong...
slider/
- cell.tsx <-- Cell
- config.ts
- input.tsx <-- Field
// slider/cell.tsx
import React from 'react'
import type { FunctionComponent } from 'react'
import type { SliderCellProps } from './types'
import './styles.scss'
const Cell: FunctionComponent<SliderCellProps> = (props) => {
const { cellData } = props
if (!cellData) return null
return (
<input className="slider-cell" type="slider" value={cellData} disabled />
)
}
export default Cell
// slider/input.tsx
import React from 'react'
import type { FunctionComponent } from 'react'
import { useFieldType } from 'payload/components/forms'
import { Label } from 'payload/components/forms'
import { validateNumberInRange } from './config'
import { SliderInputProps } from './types'
import './styles.scss'
const BASE_CLASS = 'slider'
const Input: FunctionComponent<SliderInputProps> = (props) => {
const { path, label, required, min, max, admin } = props
const { value = 0, setValue } = useFieldType<number>({
path,
validate: validateNumberInRange(min, max),
})
return (
<div className={BASE_CLASS}>
<Label htmlFor={path} label={label} required={required} />
<input
className={`${BASE_CLASS}__input`}
type="range"
onChange={(e) => setValue(e.target.value)}
value={value}
min={min}
max={max}
step={admin.step}
/>
</div>
)
}
export default Input
// slider/config.ts
import get from 'lodash/get'
import type { Field, NumberField } from 'payload/types'
import Cell from './cell'
import Input from './input'
export const validateNumberInRange =
(min = 0, max = 100) =>
(value: number): true | string => {
return (
(value >= min && value <= max) || 'Value must be between the min and max'
)
}
type SliderProps = Omit<NumberField, 'type'>
const Slider = ({
name,
min = 0,
max = 100,
...overrides
}: SliderProps): Field => ({
required: true,
...(overrides ?? {}),
name,
type: 'number',
validate: validateNumberInRange(min, max),
min,
max,
admin: {
step: get(overrides, ['admin', 'step'], 5),
components: {
Field: Input,
Cell,
},
},
})
export default Slider
In case anyone is wondering, it seems to be related to the use of useFieldType
inside slider/input.tsx
. If I change that to useField
everything works as expected.