challenges

AI Style Generator: Part 3 (Solution)

You can find a summary of the code updates in this pull request . Read on for explanations.

1. New state and type

First, let’s set up the state in src/hooks/use-quote-styles.ts to receive the new color data.

New state type

The plan is to replace the quote state (which can only contain a string, the quote itself), with a quoteProperties state. The quoteProperties value will be an object with this shape:

types/index.ts

export interface QuoteProperties {
  quote: string;
  colors: {
    text: string;
    background: string;
  };
  // include font later
}

We’ll define and export this type in src/types/index.ts, since we’ll eventually need this type for the revised QuoteContent props as well.

Create state

Next we’ll delete the quote state and replace it with quoteProperties in src/hooks/use-quote-styles.ts:

use-quote-styles.ts

// ...
import type { QuoteProperties, Status } from "@/types";
 
function useQuoteStyles() {
  const [quoteProperties, setQuoteProperties] =
    React.useState<QuoteProperties>();
  const [status, setStatus] = React.useState<Status>("idle");
  const [error, setError] = React.useState<string>();
  // ...
 
  return { 
    status, 
    error, 
    quoteProperties, 
    fetchQuoteStyles 
  };
}

Eventually, we’ll update the destructured output of useQuoteStyles in src/app/page.tsx, but we’ll wait until later and make all the necessary changes to that file at once. Let’s continue to focus on useQuoteStyles for now.

2. Update state with response

We know from src/app/api/get-quote-styles/route.ts that the response should have the shape of the QuoteProperties type. But it’s good to check and be sure, just in case.

Validate JSON

As the JSON gets more complicated, so will the logic to validate it. Let’s replace the current simple conditional with an isValidJson function:

before

const json = await response.json();
if (!json?.quote) {
  throw new Error("Malformed response");
}

after

const json = await response.json();
if (!isValidJson(json)) {
  throw new Error("Malformed response");
}

The type of the argument for isValidJson will be any, since the output of await response.json() is any

Here’s the isValidJson function. It can be defined outside the useQuoteStyles function because it takes input and doesn’t need to use the json variable defined within useQuoteStyles. It should be ouside the useQuoteStyles function because isValidJson doesn’t need to be (and shouldn’t be) re-defined every time useQuoteStyles runs (on state update, for example).

// ...
const isValidJson = (json: any) => {
  return json?.quote 
    && json?.colors?.text 
    && json?.colors?.background;
};
 
function useQuoteStyles() {
  // ...
}

Note: There are tools to help with this type of validation (such as zod ), but these tools are outside the scope of this workshop.

3. Use state for Card

Finally, we need to update how the state is consumed in src/app/page.tsx and src/components/QuoteContent/QuoteContent.tsx

Update QuoteContent props

In src/app/page.tsx, we’ll update the destructuring of return values from useQuoteStyles to include the new quoteProperties state instead of the old quote state. Then we’ll pass quoteProperties to the QuoteContent component (we’ll update the props in QuoteContent.tsx next).

page.tsx before

export default function Home() {
  const { 
    status, 
    error, 
    quote, 
    fetchQuoteStyles 
  } = useQuoteStyles();
 
  return (
    <main>
      {/* ... */}
      <QuoteContent
        status={status}
        quote={quote}
        error={error}
      />
    </main>
  );
}

page.tsx after

export default function Home() {
  const { 
    status, 
    error, 
    quoteProperties, 
    fetchQuoteStyles 
  } = useQuoteStyles();
 
  return (
    <main>
      {/* ... */}
      <QuoteContent
        status={status}
        quoteProperties={quoteProperties}
        error={error}
      />
    </main>
  );
}

Update Card props

Finally, in src/components/QuoteContent/QuoteContent.tsx, we’ll change the quote: string prop to quoteProperties: QuoteProperties.

QuoteContent.tsx

// ...
import type { QuoteProperties, Status } from "@/types";
 
// ...
export interface QuoteContentProps {
  status: Status;
  // optional, since may be undefined
  quoteProperties?: QuoteProperties;
  error?: string;
}
 
function QuoteContent({ 
  status,
  quoteProperties, 
  error 
}: QuoteContentProps) {
  // ...
}

And destructure quote and colors off of quoteProperties so we can display the quote and pass the colors to Card.

QuoteContent.tsx

function QuoteContent({ 
  status,
  quoteProperties, 
  error 
}: QuoteContentProps) {
  // ...
 
  if (quoteProperties) {
    const { quote, colors } = quoteProperties;
    return (
      <Card 
        textColor={colors.text}
        backgroundColor={colors.background}
      >
        {quote}
      </Card>
    );
  }
  // ...
}

Because Card has already been set up to use the props for styles, we should not have to edit Card at all to see the colors.

You may or may not agree with the colors that OpenAI chooses (and the colors aren’t always that consistent), but we do get colors displayed with the quote!

UI with a button to generate a random quote. A quote with green background and white text is shown beneath the button.

Next up

In the next workshop, we’ll get a font suggestion from OpenAI, and also correct the grammar and gendered language.

Workshops in this series

  1. OpenAI Node SDK
  2. Prompt engineering: color
  3. Apply color response
  4. Prompt engineering: quote cleanup and font
  5. Apply font response
  6. Quote entry (coming October 17, 2024)