Discussion about a Next.js project and seeking a front-end developer for a Sanity/Next.js/Supabase project.

11 replies
Last updated: Aug 31, 2023
😿 anybody keen to help me in my useFormValue quest, i’ve posted two code files showing what i’m trying to do and failing to do: https://sanity-io-land.slack.com/archives/C9Z7RC3V1/p1692749534746789?thread_ts=1692411026.271349&cid=C9Z7RC3V1
AI Update

I can't access the Slack thread you linked, but I can help you troubleshoot common useFormValue issues! Here are the most frequent gotchas and solutions:

Common Issues & Solutions

1. Context Error: "FormBuilder: missing context value"

This happens when you try to use useFormValue outside the form builder context. The hook only works inside custom input components, not in document actions, structure builders, or standalone components.

// āŒ Won't work - outside form context
export const myDocumentAction = (props) => {
  const value = useFormValue(['field']) // Error!
}

// āœ… Works - inside custom input component
export const MyInput = (props) => {
  const value = useFormValue(['field']) // Good!
  return props.renderDefault(props)
}

2. Path Array Syntax

The hook requires an array path, even for top-level fields:

// āŒ Wrong
const value = useFormValue('myField')

// āœ… Correct
const value = useFormValue(['myField'])

// āœ… For nested fields
const value = useFormValue(['parent', 'child', 'field'])

// āœ… For the entire document
const document = useFormValue([])

3. Accessing Values in Array Items

When working with array items, you need to use the path prop to navigate correctly:

export const CustomArrayItem = (props) => {
  const { path } = props
  const parentPath = path.slice(0, -1) // Remove current item index
  
  // Access sibling field in same array item
  const siblingValue = useFormValue([...parentPath, 'siblingField'])
}

4. Undefined/Null Values

useFormValue returns undefined if the field doesn't exist or hasn't been set yet. Always handle this:

const MyInput = (props) => {
  const otherField = useFormValue(['otherField'])
  
  useEffect(() => {
    if (otherField) { // Guard against undefined
      // Do something with the value
    }
  }, [otherField])
}

5. Reactive Updates

The hook automatically re-renders when the watched field changes, so use it in useEffect dependencies:

const MyInput = (props) => {
  const category = useFormValue(['category'])
  const [options, setOptions] = useState([])
  
  useEffect(() => {
    // This runs whenever category changes
    fetchOptionsForCategory(category).then(setOptions)
  }, [category]) // Include in dependencies
}

Quick Debugging Checklist

  1. Are you using useFormValue inside a custom input component?
  2. Is your path an array? ['field'] not 'field'
  3. Are you handling undefined values?
  4. For Studio v3, did you import from 'sanity' not '@sanity/form-builder'?

If you can share the specific error message or describe what's not working, I can give you more targeted help! Common symptoms are: values showing as undefined, component not re-rendering, or context errors.

šŸ‘‹ What does the schema for this document look like?
good morning. it’s a bit messy atm. the component I am bringing in:
{
      title: '🚧 Newsletter Tool: Nerd Edition',
      name: 'NewsletterSend',
      type: 'sendButton',
      components: {
        input: SendEmail
      },
      options: {
        title: 'title', // Pass the title field value to the component
        body: 'body' // Pass the body field value to the component
      }
    }
the full schema:

// /lib/sanity/schemas/post.ts

// import SendButtonInput from './SendButtonInput';
import SendEmail from './SendEmail';

const schema = {
  name: 'post',
  title: 'Post',
  type: 'document',
  initialValue: () => ({
    publishedAt: new Date().toISOString()
  }),
  fields: [
    {
      name: 'featured',
      title: 'Mark as Featured',
      type: 'boolean',
      layout: 'radio'
    },
    {
      name: 'title',
      placeholder: 'Enter title here',
      title: 'Title',
      type: 'string'
    },
    {
      name: 'publishedAt',
      title: 'Published at',
      type: 'datetime'
    },
    {
      name: 'excerpt',
      title: 'Excerpt / Subtitle',
      description:
        'The excerpt is used in blog feeds and as the subtitle in Newsletters, and also for search results',
      type: 'text',
      rows: 3,
      validation: (Rule) => Rule.max(200)
    },
    {
      name: 'preface',
      title: 'Preface',
      description: 'Optional note from Solana/Editor preceding the article. ',
      type: 'text'
    },
    {
      name: 'body',
      title: 'Body',
      type: 'blockContent'
    },
    {
      name: 'author',
      title: 'Author',
      type: 'reference',
      to: { type: 'author' }
    },
    {
      name: 'section',
      title: 'Section',
      type: 'string',
      options: {
        list: [
          { title: 'The Wire', value: 'the-wire' },
          { title: 'The Industry', value: 'the-industry' },
          { title: 'The White Pill', value: 'the-white-pill' }
          // Add more predefined sections as needed
        ]
      }
    },
    {
      name: 'categories',
      title: 'Categories',
      type: 'array',
      of: [{ type: 'reference', to: { type: 'category' } }]
    },
    {
      name: 'mainImage',
      title: 'Main image',
      type: 'image',
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative text',
          description: 'Important for SEO accessiblity.'
        }
      ],
      options: {
        hotspot: true
      }
    },
    {
      name: 'slug',
      title: 'Slug',
      type: 'slug',
      options: {
        source: 'title',
        maxLength: 96
      }
    },
    {
      title: '🚧 Newsletter Tool: Nerd Edition',
      name: 'NewsletterSend',
      type: 'sendButton',
      components: {
        input: SendEmail
      },
      options: {
        title: 'title', // Pass the title field value to the component
        body: 'body' // Pass the body field value to the component
      }
    }
    // {
    //   title: '🚧 Newsletter Tool: Nerd Edition',
    //   name: 'NewsletterSend',
    //   type: 'sendButton',
    //   components: {
    //     input: SendButtonInput
    //   },
    //   options: {
    //     title: 'title', // Pass the title field value to the component
    //     body: 'body' // Pass the body field value to the component
    //   }
    // }
  ],

  preview: {
    select: {
      title: 'title',
      author: 'author.name',
      media: 'mainImage'
    },

    prepare(selection) {
      const { author } = selection;
      return Object.assign({}, selection, {
        subtitle: author && `by ${author}`
      });
    }
  }
};

export default schema;
Ah, I think I just saw the problem. I don’t believe you need to put the brackets around the variable you’re assigning to what’s returned from
UseFormValue
. For example, this should work:
const title = useFormValue(['title']);
Presently trying this:
// lib/sanity/schemas/SendEmail.jsx
import { toHTML } from '@portabletext/to-html';
import React from 'react';
import { useMemo } from 'react';
import { useFormValue } from 'sanity';

const components = {
  types: {
    image: ({ value }) => {
      if (value && value.asset && value.asset._ref) {
        return `<img src="${value.asset._ref}" />`;
      } else {
        return '';
      }
    },
    embed: ({ value }) => {
      if (value && value.url) {
        return `<iframe src="${value.url}" />`;
      } else {
        return '';
      }
    }
  }
};

export default function SendEmail(props) {
  const form = useFormValue([]);
  const title = useFormValue(['title']);
  const section = useFormValue(['section']);
  const subTitle = useFormValue(['excerpt']);
  const preface = useFormValue(['preface']);
  const body = useFormValue(['body']);
  const bodyHtml = toHTML(body, { components });

  // const authorName = 'Author Name';
  // const authorSlug = 'author-slug';
  // const authorImageUrl = 'author-image';

  console.log('form', form);
  console.log('title', title);
  console.log('section', section);
  console.log('subTitle', subTitle);
  console.log('preface', preface);
  console.log('bodyHtml', bodyHtml);

  const [formValues, setFormValues] = React.useState({
    title: '',
    section: '',
    subTitle: '',
    preface: '',
    bodyHtml: ''
  });

  React.useEffect(() => {
    setFormValues({
      title: title,
      section: section,
      subTitle: subTitle,
      preface: preface,
      bodyHtml: bodyHtml
    });
  }, [title, section, subTitle, preface, bodyHtml]);

  const handleSendEmail = async () => {
    // Prepare the request body
    // I only want to send the title, section, and subTitle right now
    const requestBody = {
      title: formValues.title,
      subTitle: formValues.subTitle,
      section: formValues.section
    };

    console.log('Request body:', requestBody);

    try {
      const response = await fetch('/api/send-email', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(requestBody)
      });

      if (response.ok) {
        setEmailSent(true);
      } else {
        console.error('Email sending failed');
      }
    } catch (error) {
      console.error('An error occurred:', error);
    }
  };

  return (
    <div className="space-y-2 border p-4">
      <h2 className="text-xl font-bold">Send Email</h2>
      <div className="flex flex-col space-y-2">
        <pre>{title}</pre>
        <button onClick={handleSendEmail}>Send Email</button>
      </div>
    </div>
  );
}
i just put up a commit of the three important files in this puzzle. maybe easier to see the whole picture: https://github.com/Pirate-Wires/pirate-wires/commit/c09c58d3e6a1d1ef54de4605534af3d3a9c55f08
the actual passing of values to the template isn’t so much the problem, as i am hardcoding those values in the route presently. the email fires off and that is fine. i’m meanwhile just trying to get the values in the right shape in the component and pass the req body successfully. no values log in the route and my req res are all a mess. getting 500 while the email actually does get sent. kind of dealing with an assortment of problems, some more next.js 13 api route stuff, but then the confusing aspect of, ā€œare my useFormValues getting put into the proper for (req body) and can the route even consume them if they wereā€.
user M
I shared this screen-share a moment ago with
user S
who has been helping me try to figure this out: https://www.loom.com/share/f2a33f455fec405b85c9a04250e6007e?sid=091352b7-7502-4c86-833e-0422e578fcfd
Hmm; it seems congruent with any way I am used to these things being handled. That said, I am not privy to the app router yet and there seem to be some discrepancies between your route as-written and some of the doc patterns in terms of things like import resolution, some syntax, etc.
Not saying that's the case but mostly because I wouldn't know one way or another from experience.

Seems very Next-y though to seemingly work but not-quite-work silently. If you have a grip on the new router might be worth double-checking that aspect of it if you haven't yet.
Ah, it seems I am getting warmer. Thanks to some clues in the ā€˜doc pattern’ you pointed out. I am logging everything I need now in the route successfully. I’ll post the updated code here in a moment. For those following along, you have my sympathy. It was largely a matter of NextRequest, NextResponse methods being written as for NextApiRequest, NextApiResponse, and a handful of other next-y stuff.
Looking for a front-end dev if y’all know anyone with some time in the next month or more. We have a sanity/next.js/supabase project with a fair amount of complexity and are looking for someone comfortable with the ā€œfull stackā€: qroq, servless functions, vercel, tailwind, state management in next.js 13 / react, etc… quite a lot of api work on the front too.
I think it's unlikely that there will be high visibility deep into this one thread, so you might want to make sure that goes in jobs , unless you meant to narrow it to people either of us know šŸ™‚
That said, aside from Tailwind knowledge that's my stack, Supabase included, so I may find myself around people who'll fit well and accordingly will keep my eyes open and ears peeled!

I am happy to answer any integration questions between the two platforms here in the open where others could benefit, otherwise, as a courtesy, if you have Supabase-only questions feel free to DM me and I'll see if I can help (aside from what comes from, and until you get, the professional help, that is)

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?