Creating a UI Field to display chart data

default discord avatar
asibilialast year
1 1

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.

  • Selected Answer
    discord user avatar
    JarrodMFlesch
    last year

    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!

    4 replies
  • default discord avatar
    asibilialast year

    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)

  • discord user avatar
    JarrodMFlesch
    last year

    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.

  • default discord avatar
    asibilialast year

    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
  • default discord avatar
    asibilialast year

    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.

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.