Skip to content

Instantly share code, notes, and snippets.

@kmelve
Created February 7, 2020 06:13
Show Gist options
  • Save kmelve/e34feccafe8e9ee3c7e09d60ee3fd4d2 to your computer and use it in GitHub Desktop.
Save kmelve/e34feccafe8e9ee3c7e09d60ee3fd4d2 to your computer and use it in GitHub Desktop.
Tim’s conditional fields for Sanity.io
import PropTypes from 'prop-types'
import React from 'react'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import {setIfMissing} from 'part:@sanity/form-builder/patch-event'
// FormBuilderInput automatically generates fields from a schema
import {FormBuilderInput} from 'part:@sanity/form-builder'
// a Higher Order Component that passes document values as props
import {withDocument} from 'part:@sanity/form-builder'
class confitionalFields extends React.PureComponent {
static propTypes = {
type: PropTypes.shape({
title: PropTypes.string,
name: PropTypes.string
}).isRequired,
level: PropTypes.number,
value: PropTypes.shape({
_type: PropTypes.string
}),
focusPath: PropTypes.array.isRequired,
onFocus: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func.isRequired
}
firstFieldInput = React.createRef()
handleFieldChange = (field, fieldPatchEvent) => {
const {onChange, type} = this.props
// Whenever the field input emits a patch event, we need to make sure to each of the included patches
// are prefixed with its field name, e.g. going from:
// {path: [], set: <nextvalue>} to {path: [<fieldName>], set: <nextValue>}
// and ensure this input's value exists
onChange(fieldPatchEvent.prefixAll(field.name).prepend(setIfMissing({_type: type.name})))
}
focus() {
this.firstFieldInput.current.focus()
}
render() {
console.log(this.props)
const {document, type, value, level, focusPath, onFocus, onBlur} = this.props
let condition = this.props && this.props.value && this.props.value.condition;
return (
<Fieldset level={level} legend={type.title} description={type.description}>
<div>
{type.fields[0].type.fields
.map((field, i) => (
// Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component
// for the given field type
<FormBuilderInput
level={level + 1}
ref={i === 0 ? this.firstFieldInput : null}
key={field.name}
type={field.type}
value={value && value[field.name]}
onChange={patchEvent => this.handleFieldChange(field, patchEvent)}
path={[field.name]}
focusPath={focusPath}
onFocus={onFocus}
onBlur={onBlur}
/>
))}
<br/>
{type.fields[1].type.fields
.filter(field => (
field.name === condition ? true : false
))
.map((field, i) => (
// Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component
// for the given field type
<FormBuilderInput
level={level + 1}
ref={i === 0 ? this.firstFieldInput : null}
key={field.name}
type={field.type}
value={value && value[field.name]}
onChange={patchEvent => this.handleFieldChange(field, patchEvent)}
path={[field.name]}
focusPath={focusPath}
onFocus={onFocus}
onBlur={onBlur}
/>
))}
</div>
</Fieldset>
)
}
}
export default withDocument(confitionalFields)
@amitkumar
Copy link

Thanks for this! Exactly what I needed.

@nboliver
Copy link

This is useful, but all the inline objects need to be separated out and defined as a global schema type for use with GraphQL which is not ideal since they are specific to the parent object (link in this case).

@guayom
Copy link

guayom commented Jan 14, 2021

I found the same issue. Is there a way to use this solution with GraphQL?

@JonathanSaudhof
Copy link

JonathanSaudhof commented Feb 4, 2021

Hey! Perfect. Currently, I try to wrap my head around building a custom input component, based on a document.
I still wonder if I need to do it in a class component. However, let me describe my problem.

What I try to achieve: Set a document were I define all the fields that need to be rendered (reviewTemplate).
-> create another document (review) where I select the template and the field will be rendered accordingly.

The Input fields shall show up like a "native" sanity ui component.

It works with an HTML input field. but on every change event, it loses the focus and I have to click the input field again to type any further (just one character at a time).
So I played around with the formbuilderinput. But unfortunately, this error message pops up: func.apply is not a function.

Is this the right place to comment with code? Or where can I ask for help? Do you have documentations for the api to
build such a custom form?

THX. :)

import React, { Component } from 'react'
import { withDocument } from 'part:@sanity/form-builder'
import FormField from 'part:@sanity/components/formfields/default'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import { FormBuilderInput } from 'part:@sanity/form-builder'
import { Document, Type } from '../../@types'
import PatchEvent, { set, unset } from 'part:@sanity/form-builder/patch-event'
import client from 'part:@sanity/base/client'
import { v4 } from 'uuid'
interface InputComponentProps {
  type: Type
  document?: Document
  value: any | any[]
  onChange: Function
  markers: any[]
  level: number
  onBlur: Function
}

interface Evaluation {
  document: {
    productReviewFeatures: any
  }
}

const EvaluationContainer: React.FC = ({
  type,
  document,
  value,
  markers,
  level,
  onChange,
  onBlur,
  onFocus,
  focusPath
}) => {
  const { productReviewFeatures } = document
  const { title, description } = type
  console.log('focus path', focusPath)
  const [isLoading, setIsloading] = React.useState<Boolean>(true)
  const [evaluationFeatures, setEvaluationFeautures] = React.useState([])
  const [evaluation, setEvaluation] = React.useState([])

  const getSubEvaluationStructure = subFeatures => {
    let subFeatureStructure = []
    subFeatures.map(sub => {
      subFeatureStructure.push({
        _type: 'evaluation',
        type: sub.type,
        name: sub.name
      })
    })
    return subFeatureStructure
  }

  const getMainEvaluationStructure = mainFeatures => {
    let mainEvaluationStructure = []
    mainFeatures.map(mainFeature => {
      console.log('mainFeature', mainFeature)
      mainEvaluationStructure.push({
        _type: 'mainFeatureEvaluation',
        name: mainFeature.name,
        subFeatureEvaluation: getSubEvaluationStructure(
          mainFeature.subFeatureEvaluation || mainFeature.subFeatures
        )
      })
    })

    return mainEvaluationStructure
  }

  React.useEffect(async () => {
    if (productReviewFeatures && productReviewFeatures._ref) {
      try {
        const document = await client.fetch(`*[_id =="${productReviewFeatures._ref}"][0]`)
        if (document && document.mainFeatures) {
          console.log('document', document)
          setEvaluationFeautures(document.mainFeatures)
          setEvaluation(
            getMainEvaluationStructure(document.reviewEvaluation || document.mainFeatures)
          )
          setIsloading(false)
        }
      } catch (error) {
        console.log(error)
      }
    }
  }, [productReviewFeatures._ref])

  if (isLoading) {
    return <div>Data Loading</div>
  }

  const handleChange = (event, { j, i }) => {
    const newEvaluation = evaluation

    console.log('handle', (newEvaluation[j].subFeatureEvaluation[i].value = event.target.value))
    setEvaluation([...newEvaluation])
    console.log('evaluation', evaluation)
    onChange(PatchEvent.from(set(evaluation)))
  }

  console.log('value', value)

  return (
    <Fieldset legend={title} description={description}>
      {evaluationFeatures &&
        evaluationFeatures.map((feature, j) => {
          return (
            <div key={v4()}>
              <p>{feature.name}</p>
              {feature.subFeatures &&
                feature.subFeatures.map((subFeature, i) => (
                  <FormField label={subFeature.name} description={subFeature.description}>
                    <input
                      type="text"
                      value={value[j].subFeatureEvaluation[i].value}
                      name={subFeature.name}
                      onChange={event => handleChange(event, { j, i })}
                    />
                    <FormBuilderInput
                      level={level + 1}
                      ref={i === 0 ? React.createRef() : null}
                      key={subFeature.name}
                      type={subFeature.type}
                      value={value[j].subFeatureEvaluation[i].value}
                      onChange={event => handleChange(event, { j, i })}
                      path={[`${j}${i}`]}
                      focusPath={focusPath}
                      onFocus={onFocus}
                      onBlur={onBlur}
                    />
                  </FormField>
                ))}
            </div>
          )
        })}
    </Fieldset>
  )
}

const exportData = [
  {
    type: 'mainFeature',
    title: 'Connectivity',
    subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }]
  },
  { type: 'mainFeature', subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }] }
]

export default withDocument(EvaluationContainer)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment