#Server actions with my own backend

1 messages · Page 1 of 1 (latest)

zenith needleBOT
#

Hi @velvet tusk. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You can use either approach with Next.js, but Server Actions offer several advantages when working with your own backend like Spring Boot.

Using Server Actions with your own backend

Server Actions allow you to create server-side functions that can be called directly from your components. When using them with your own backend, you would:

  1. Create a Server Action that calls your Spring Boot API
  2. Use this action in your forms or event handlers
'use server'

export async function submitToBackend(formData: FormData) {
  // Extract data from the form
  const data = {
    name: formData.get('name'),
    email: formData.get('email'),
    // other fields...
  }

  // Call your Spring Boot API
  const response = await fetch('<https://your-spring-boot-api.com/endpoint',> {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  })

  // Handle the response
  if (!response.ok) {
    throw new Error('Failed to submit form')
  }

  return await response.json()
}

Then in your component:

import { submitToBackend } from './actions'

export default function MyForm() {
  return (
    <form action={submitToBackend}>
      <input name="name" type="text" />
      <input name="email" type="email" />
      <button type="submit">Submit</button>
    </form>
  )
}

Benefits of using Server Actions

  1. Security: Server Actions keep sensitive information like API keys on the server Server Actions and Mutations

  2. Progressive enhancement: Forms with Server Actions work even if JavaScript is disabled Server Actions and Mutations

#
  1. Simplified code: No need to create separate API routes in Next.js to proxy requests to your backend

  2. Improved UX: After hydration, the browser doesn't refresh on form submission, creating a smoother experience

Direct submission to your backend

Alternatively, you could submit directly to your Spring Boot backend using client-side code:

'use client'

export default function MyForm() {
  async function handleSubmit(event) {
    event.preventDefault()
    const formData = new FormData(event.target)
    
    const response = await fetch('<https://your-spring-boot-api.com/endpoint',> {
      method: 'POST',
      body: formData,
    })
    
    // Handle response
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" type="text" />
      <input name="email" type="email" />
      <button type="submit">Submit</button>
    </form>
  )
}

This approach is similar to how you would handle forms in the Pages Router Forms and Mutations.

Recommendation

I recommend using Server Actions as they provide better security, progressive enhancement, and a smoother user experience. They're also the recommended approach for the App Router in Next.js.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@velvet tusk Kapa.ai is still learning and improving, please let me know how I did by reacting below.

velvet tusk
#

@zenith needle Can I validate the form data on the client side and avoid it on the frontend server side since my Spring Boot API has its own validation?

zenith needleBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Yes, you can validate form data on the client side and skip server-side validation in your Next.js application when your Spring Boot API already handles validation. This is a valid approach, though there are some considerations to keep in mind.

Client-side validation options

You can implement client-side validation using HTML attributes or JavaScript libraries:

  1. Basic HTML validation:

    <form>
      <input type="email" name="email" required />
      <input type="text" name="name" minLength="2" required />
      <button type="submit">Submit</button>
    </form>
    
  2. JavaScript validation libraries like Zod, Yup, or React Hook Form for more advanced validation.

Server Actions without validation

If you're using Server Actions to submit to your Spring Boot API, you can skip the validation step and just pass the data through:

'use server'

export async function submitToBackend(formData: FormData) {
  // No validation here - just forward to your API
  const response = await fetch('<https://your-spring-boot-api.com/endpoint',> {
    method: 'POST',
    body: formData, // Or transform to JSON if needed
  })

  // Handle API response (which may include validation errors)
  const data = await response.json()
  
  if (!response.ok) {
    // Return validation errors from your API
    return { errors: data.errors }
  }
  
  return { success: true, data }
}

Considerations

While this approach can work, there are some things to consider:

  1. User Experience: Client-side validation provides immediate feedback without waiting for server responses Forms and Mutations.

  2. Network Efficiency: Validating on the client prevents unnecessary API calls with obviously invalid data.

#
  1. Security: Remember that client-side validation is for user experience only. Your Spring Boot API must still validate all inputs for security Understanding XSS Attacks.

  2. Error Handling: You'll need to handle and display validation errors returned from your Spring Boot API.

Displaying API validation errors

You can use React's state management to display validation errors from your API:

'use client'

import { useActionState } from 'react'
import { submitToBackend } from './actions'

export default function Form() {
  const [state, formAction, pending] = useActionState(submitToBackend, {})

  return (
    <form action={formAction}>
      <input type="email" name="email" required />
      {state?.errors?.email && <p>{state.errors.email}</p>}
      
      <button disabled={pending}>Submit</button>
    </form>
  )
}

This approach is valid and can work well when your backend already has robust validation. Just ensure you properly handle and display any validation errors returned from your Spring Boot API to maintain a good user experience.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@velvet tusk Kapa.ai is still learning and improving, please let me know how I did by reacting below.

velvet tusk
#

@zenith needle what is the recommended library for client side validation

zenith needleBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, Next.js documentation recommends several libraries for client-side form validation, with Zod being the most frequently mentioned.

Recommended validation libraries

Zod

Zod is the most commonly referenced validation library in the Next.js documentation. It's used for schema validation and is particularly well-integrated with Next.js for both client and server-side validation.

import { z } from 'zod'

const schema = z.object({
  email: z.string({
    invalid_type_error: 'Invalid Email',
  }),
})

// Validate form data
const validatedFields = schema.safeParse({
  email: formData.get('email'),
})

Server Actions and Mutations documentation shows how to use Zod with forms, and it's also mentioned in the Authentication documentation.

Other recommended options

Basic HTML validation

For simpler use cases, the documentation also recommends using native HTML validation attributes:

<input type="email" name="email" required />

This provides basic client-side validation without additional libraries Forms and Mutations.

Recommendation

#

Zod appears to be the most recommended and well-integrated validation library for Next.js applications. It works well with both client-side validation and Server Actions, making it a good choice for your Spring Boot integration scenario.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@velvet tusk Kapa.ai is still learning and improving, please let me know how I did by reacting below.

velvet tusk
#

can i use server components to fetch dynamic data or do i need to use client side fetching @zenith needle

zenith needleBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Yes, you can definitely use Server Components to fetch dynamic data in Next.js, and in many cases, this is the recommended approach.

Server Components for Data Fetching

Server Components are well-suited for data fetching because they:

  1. Run on the server: They can fetch data closer to your data source, reducing latency Server Components

  2. Keep sensitive information secure: API keys and tokens stay on the server Server Components

  3. Reduce client-side JavaScript: The data fetching code doesn't get sent to the browser Server Components

Here's how you can fetch data in a Server Component:

// This is a Server Component by default
export default async function Page() {
  // Fetch data directly in the component
  const data = await fetch('<https://api.vercel.app/blog'>)
  const posts = await data.json()
  
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

This approach works for dynamic data too. You can control caching behavior with options:

// For dynamic data that shouldn't be cached (similar to getServerSideProps)
const dynamicData = await fetch('<https://api.example.com/data',> { cache: 'no-store' })

// For data that should be revalidated periodically
const revalidatedData = await fetch('<https://api.example.com/data',> {
  next: { revalidate: 10 }, // Revalidate every 10 seconds
})
#

Data Fetching and Caching

When to Use Client-Side Fetching

While Server Components are recommended for most data fetching scenarios, client-side fetching is still useful in certain cases:

  1. User-specific data after initial load
  2. Frequently updating data that doesn't need SEO
  3. Event-based data fetching (like button clicks)
  4. Components that rely on browser APIs

For client-side fetching, Next.js recommends using SWR or React Query rather than plain useEffect:

'use client'

import useSWR from 'swr'

const fetcher = (url) => fetch(url).then((r) => r.json())

export default function Posts() {
  const { data, error, isLoading } = useSWR(
    '<https://api.vercel.app/blog',>
    fetcher
  )

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error</div>

  return (
    <ul>
      {data.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Client-side Fetching

Recommendation

For most dynamic data fetching scenarios, Server Components are the recommended approach in Next.js App Router. They provide better performance, security, and SEO benefits. Use client-side fetching only when you specifically need interactivity or real-time updates after the initial page load.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@velvet tusk Kapa.ai is still learning and improving, please let me know how I did by reacting below.

velvet tusk
#

show me the code for a form that fetches countries from an api, then fetches the states based on the country id @zenith needle

zenith needleBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

I'll show you how to create a form that fetches countries from an API and then fetches states based on the selected country ID using Next.js. This is a common pattern for location selection forms.

Here's how you can implement this using Server Components for the initial data fetch and Client Components for the interactive parts:

// app/location-form/page.tsx
import CountryStateForm from './CountryStateForm'

// Server Component to fetch the initial countries data
export default async function LocationFormPage() {
  // Fetch countries on the server
  const countries = await fetchCountries()
  
  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">Location Selector</h1>
      <CountryStateForm initialCountries={countries} />
    </div>
  )
}

// Function to fetch countries
async function fetchCountries() {
  const res = await fetch('<https://api.example.com/countries',> { cache: 'force-cache' })
  
  if (!res.ok) {
    throw new Error('Failed to fetch countries')
  }
  
  return res.json()
}

Now, let's create the interactive form component:

// app/location-form/CountryStateForm.tsx
'use client'

import { useState, useEffect } from 'react'

export default function CountryStateForm({ initialCountries }) {
  const [countries] = useState(initialCountries)
  const [states, setStates] = useState([])
  const [selectedCountry, setSelectedCountry] = useState('')
  const [selectedState, setSelectedState] = useState('')
  const [isLoadingStates, setIsLoadingStates] = useState(false)
#

// Fetch states when country selection changes
useEffect(() => {
async function fetchStates() {
if (!selectedCountry) {
setStates([])
return
}

  setIsLoadingStates(true)
  
  try {
    const res = await fetch(`/api/states?countryId=${selectedCountry}`)
    
    if (!res.ok) {
      throw new Error('Failed to fetch states')
    }
    
    const data = await res.json()
    setStates(data)
  } catch (error) {
    console.error('Error fetching states:', error)
    setStates([])
  } finally {
    setIsLoadingStates(false)
  }
}

fetchStates()

}, [selectedCountry])

const handleCountryChange = (e) => {
setSelectedCountry(e.target.value)
setSelectedState('') // Reset state selection when country changes
}

const handleSubmit = (e) => {
e.preventDefault()
console.log('Form submitted with:', { selectedCountry, selectedState })
// Process form submission here
}

#

return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="country" className="block mb-2">Country</label>
<select
id="country"
value={selectedCountry}
onChange={handleCountryChange}
className="w-full p-2 border rounded"
required
>
<option value="">Select a country</option>
{countries.map((country) => (
<option key={country.id} value={country.id}>
{country.name}
</option>
))}
</select>
</div>

  <div>
    <label htmlFor="state" className="block mb-2">State/Province</label>
    <select
      id="state"
      value={selectedState}
      onChange={(e) => setSelectedState(e.target.value)}
      className="w-full p-2 border rounded"
      disabled={!selectedCountry || isLoadingStates}
      required
    >
      <option value="">
        {isLoadingStates 
          ? 'Loading states...' 
          : selectedCountry 
            ? 'Select a state' 
            : 'Select a country first'}
      </option>
      {states.map((state) => (
        <option key={state.id} value={state.id}>
          {state.name}
        </option>
      ))}
    </select>
  </div>
  
  <button 
    type="submit" 
    className="px-4 py-2 bg-blue-500 text-white rounded"
    disabled={!selectedCountry || !selectedState}
  >
    Submit
  </button>
</form>

)
}


Now, let's create an API route to fetch states based on country ID:

```tsx
// app/api/states/route.ts
import { NextResponse } from 'next/server'
#

export async function GET(request) {
const { searchParams } = new URL(request.url)
const countryId = searchParams.get('countryId')

if (!countryId) {
return NextResponse.json(
{ error: 'Country ID is required' },
{ status: 400 }
)
}

try {
// Fetch states from external API
const response = await fetch(
<https://api.example.com/states?countryId=${countryId},>
{ cache: 'no-store' } // Don't cache this dynamic data
)

if (!response.ok) {
  throw new Error('Failed to fetch states')
}

const states = await response.json()
return NextResponse.json(states)

} catch (error) {
console.error('Error fetching states:', error)
return NextResponse.json(
{ error: 'Failed to fetch states' },
{ status: 500 }
)
}
}


This implementation follows Next.js best practices by:

1. Using a Server Component for the initial data fetch of countries
2. Using a Client Component for the interactive form with dynamic state fetching
3. Creating an API route to proxy requests to the external API
4. Implementing proper loading states and error handling

You can customize the API endpoints and response formats to match your actual API structure.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@velvet tusk Kapa.ai is still learning and improving, please let me know how I did by reacting below.

velvet tusk
#

so i need to use a client component for dynamic state fetching? is it possible with server components? @zenith needle

zenith needleBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, there's an important distinction to make about dynamic data fetching with Server Components versus Client Components in Next.js.

Server Components and Dynamic Data

Yes, you can use Server Components to fetch dynamic data, but there's a key limitation: Server Components run during the request/build cycle and cannot re-render based on client-side state changes.

As mentioned in one of the GitHub issues:

"Am I misunderstanding the idea of server-side components, and they are only for initially loaded data?" Github Issue #47123

This is a common confusion. Server Components are excellent for fetching data during the initial render or when the URL/route changes, but they cannot respond to client-side state changes like form inputs without a full page refresh or client-side intervention.

Patterns for Dynamic Dependent Fetching

For your specific use case (fetching states based on selected country), you have several options:

1. Client Component Approach (as shown in previous example)

This is the most straightforward approach for dependent, interactive data fetching:

'use client'
// Client component that handles both country selection and state fetching

2. Server Component + Client Interactivity

You can use a pattern where:

  • Server Component fetches initial data (countries)
  • Client Component handles the user interaction
  • API Route or Server Action fetches the dependent data (states)

3. URL-based Approach

If you want to leverage Server Components more, you can make the country selection change the URL, which would trigger a new server render with the states data:

#
// app/location/[countryId]/page.tsx
export default async function CountryPage({ params }) {
  const { countryId } = params
  const countries = await fetchCountries()
  const states = countryId ? await fetchStates(countryId) : []
  
  return (
    <LocationForm 
      countries={countries} 
      states={states} 
      selectedCountry={countryId} 
    />
  )
}

Recommendation

For interactive forms with dependent dropdowns like your country/state example, a Client Component is typically the most appropriate solution. This is because:

  1. The interaction is client-side (user selecting a country)
  2. The dependent data (states) needs to be fetched in response to user interaction
  3. You want to avoid full page refreshes for a smooth user experience

The pattern shown in the previous example (Server Component for initial data + Client Component for interactivity) follows Next.js best practices for this type of scenario.

If you're concerned about keeping sensitive API keys secure, you can use an API Route or Server Action to proxy the requests to your external API, as shown in the previous example.

Remember that Server Components are powerful for initial and route-based data fetching, but Client Components are still necessary for truly interactive UI elements that need to respond to user input without page transitions.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: