How to access a D1 binding from a next.js tsx file (NOT .ts)?

In my next on pages app, I got a db call working on a .ts file but I'm wondering how I access that data within a tsx page? I've read all the documentation but I don't see any next.js examples of calling data within a .tsx server component, only from a .ts file (which I can't import bc of this error in the screenshot). Open to any suggestions :PES_CryHug:
"use client"
import { useState, useEffect } from 'react';
import { GET } from './api/all/route'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

useEffect(() => {
GET().then((data: any) => {
setData(data.target.value);
});
}, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

return (
<div>
<p>{data}</p>
</div>
);
}
"use client"
import { useState, useEffect } from 'react';
import { GET } from './api/all/route'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

useEffect(() => {
GET().then((data: any) => {
setData(data.target.value);
});
}, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

return (
<div>
<p>{data}</p>
</div>
);
}
No description
17 Replies
thipperz
thipperz7mo ago
I believe the issue here is that "use client" directive - you cannot call your D1 directly from a client component Try using a server action for that
real_witt
real_wittOP7mo ago
No description
real_witt
real_wittOP7mo ago
@thipperz if I get rid of that directive though, I can't use useEffect and I'm not sure how i'd get that db call to be made on page load. Maybe there's another way to achieve that that I'm not aware of
thipperz
thipperz7mo ago
Yup you should not remove "use client". Instead, create a server action that calls the database and forwards the results to your client component. A "server action" is an async function that is exported from a file that begins with the "use server" directive. For example in app/actions/index.ts:
"use server"
import { GET } from './api/route'
export async function getAction() {
return await GET()
}
"use server"
import { GET } from './api/route'
export async function getAction() {
return await GET()
}
Then you can import this new function and use it from your client components. Notice how server actions act like a bridge between client components and parts of your app that cannot be imported from them (usually because of a "server-only" clause, that is useful to avoid leaking sensitive data to the client
real_witt
real_wittOP7mo ago
Ahh ok I'm gonna try this, thanks
thipperz
thipperz7mo ago
Let me know if it works for you
real_witt
real_wittOP7mo ago
I think I'm close... ./actions.ts
"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
./page.tsx
"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

useEffect(() => {
getAction().then((data: any) => {
setData(data.target.value);
});
}, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

return (
<div className="ml-5">
<h1 className="text-center font-bold text-4xl pt-4">Wittbit CMS</h1>

<div className='flex'>
<div className='flex flex-col'>
<label htmlFor='title'>Title</label>
</div>

<div className='ml-2 flex flex-col'>
<input id="title" className='outline outline-2 ml-3' value={title} onChange={handleTitleChange} />
</div>
</div>
<p>testing data:</p>
<p>{data}</p>
</div>
);

}
"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

useEffect(() => {
getAction().then((data: any) => {
setData(data.target.value);
});
}, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

return (
<div className="ml-5">
<h1 className="text-center font-bold text-4xl pt-4">Wittbit CMS</h1>

<div className='flex'>
<div className='flex flex-col'>
<label htmlFor='title'>Title</label>
</div>

<div className='ml-2 flex flex-col'>
<input id="title" className='outline outline-2 ml-3' value={title} onChange={handleTitleChange} />
</div>
</div>
<p>testing data:</p>
<p>{data}</p>
</div>
);

}
gives me:
No description
thipperz
thipperz7mo ago
The data that you return from a server action must be serializable - what are u returning from your GET function?
real_witt
real_wittOP7mo ago
Lol great question. I've been trying to console.log it to see haha
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

console.log('testing:')
console.log(Response.json(data.results))
return Response.json(data.results)
}
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

console.log('testing:')
console.log(Response.json(data.results))
return Response.json(data.results)
}
I got this from the docs at https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-nextjs-site/#access-bindings-in-the-application and also referenced the next.js db example call in this: https://blog.cloudflare.com/blazing-fast-development-with-full-stack-frameworks-and-cloudflare
thipperz
thipperz7mo ago
I never tried importing the GET method of a route handler before lol I mean I have no idea if that code should work, but if it should, you could try awaiting the json method to finish before returning the response await Response.json(data.results) If this does not work, you could try moving that DB call to a .ts file and returning the results directly instead of having them being encapsulated by a Response For example if you move it to a lib/db/index.ts file:
export const runtime = 'edge'
import { getRequestContext } from '@cloudflare/next-on-pages'


export async function getData() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

console.log('testing:')
return data.results
}
export const runtime = 'edge'
import { getRequestContext } from '@cloudflare/next-on-pages'


export async function getData() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

console.log('testing:')
return data.results
}
Then you would import this into your server action and finally import that action in your client component
real_witt
real_wittOP7mo ago
oh let's go
No description
real_witt
real_wittOP7mo ago
axing the Response wrapper object in the return from the db call .ts file gave me some progress man why on earth would that work json is serializable right... that was the return object type. Weird.
thipperz
thipperz7mo ago
hmm did you try adding the "await" I said? Like await Response.json(data.results) because I believe Promises are not serializable
real_witt
real_wittOP7mo ago
Ok so I did a return await on the db call:
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
and on the actions.ts
./actions.ts

"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
./actions.ts

"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
and then the .tsx file:
"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

// useEffect(() => {
// getAction().then((data: any) => {
//
// // return Response.json(data.results)
// setData(data.target.value);
// });
// }, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

async function giveItToMeAlready() {
setData( () => {
const res = getAction();
console.log(res)
})
}

return (
<div className="ml-5">
<h1 className="text-center font-bold text-4xl pt-4">Wittbit CMS</h1>

<div className='flex'>
<div className='flex flex-col'>
<label htmlFor='title'>Title</label>
</div>

<div className='ml-2 flex flex-col'>
<input id="title" className='outline outline-2 ml-3' value={title} onChange={handleTitleChange} />
</div>
</div>
<p>testing data:</p>
<button onClick={giveItToMeAlready} >click to get data</button>
<p>{data}</p>
</div>
);

}
"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

const [title, setTitle] = useState('');
const [data, setData] = useState();

// useEffect(() => {
// getAction().then((data: any) => {
//
// // return Response.json(data.results)
// setData(data.target.value);
// });
// }, []);


function handleTitleChange(e: any) {
setTitle(e.target.value)
}

async function giveItToMeAlready() {
setData( () => {
const res = getAction();
console.log(res)
})
}

return (
<div className="ml-5">
<h1 className="text-center font-bold text-4xl pt-4">Wittbit CMS</h1>

<div className='flex'>
<div className='flex flex-col'>
<label htmlFor='title'>Title</label>
</div>

<div className='ml-2 flex flex-col'>
<input id="title" className='outline outline-2 ml-3' value={title} onChange={handleTitleChange} />
</div>
</div>
<p>testing data:</p>
<button onClick={giveItToMeAlready} >click to get data</button>
<p>{data}</p>
</div>
);

}
oh im an idiot. Had to take it out of the setData function. doh @thipperz thanks so much man, you were a huge help. Really appreciate it you're a life saver lol :peepoHappyhug:
thipperz
thipperz7mo ago
You're welcome! I also learned a few things from your case lol But I'm actually a bit confused about this:
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
./api/all/route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'

export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
Because I believe route handlers (any route.ts file) must return either a Response or a NextResponse object (?)
real_witt
real_wittOP7mo ago
I did get an error on returning a non-Response object from the db call at one point, but that setup above works for me so… idk lol If there is a better way to do this, I can’t find one anywhere online. In the docs or otherwise Which is weird considering how basic this is for making any kind of app I’m boutta hit the hay for tonight but I’ll show my configs tomorrow morning in here and hopefully that will help To start off, you'll need to set your env variables in your wrangler.toml file (as per the docs https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application)
./wranger.toml

#:schema node_modules/wrangler/config-schema.json
name = "gm-app"
compatibility_date = "2024-04-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "prod-gm-app"
database_id = "4523423293842983" // this will be whatever your db id was when you created it
// ...
./wranger.toml

#:schema node_modules/wrangler/config-schema.json
name = "gm-app"
compatibility_date = "2024-04-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
[[d1_databases]]
binding = "DB" # available in your Worker on env.DB
database_name = "prod-gm-app"
database_id = "4523423293842983" // this will be whatever your db id was when you created it
// ...
Then if you're using .ts you generate the types using npm run build-cf-types (found that on https://blog.cloudflare.com/blazing-fast-development-with-full-stack-frameworks-and-cloudflare) Create your route: (taken from https://developers.cloudflare.com/pages/framework-guides/nextjs/deploy-a-nextjs-site/#access-bindings-in-the-application)
./src/app/api/something/route.ts
// NOTE: the naming of this file seems to matter, I think it HAS to be route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'
export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
./src/app/api/something/route.ts
// NOTE: the naming of this file seems to matter, I think it HAS to be route.ts

import { getRequestContext } from '@cloudflare/next-on-pages'
export const runtime = 'edge'

export async function GET() {
const myDb = getRequestContext().env.DB;
const data: any = await myDb.prepare('SELECT * FROM service').all();

return await data.results;
}
Then to avoid conflicts with mixing client/server component imports, create a getter file. After @thipperz helped me with this I remember reading something on about this pattern on either stackoverflow or the next.js docs but don't remember the link. Oh well.
./src/app/actions.ts

"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
./src/app/actions.ts

"use server"
import { GET } from './api/all/route'
export async function getAction() {
return await GET()
}
Then FINALLY to be able to use the data inside your actual .tsx file with html markup:
./src/app/page.tsx

"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

async function giveItToMeAlready() {
const res = await getAction();
console.log(res)
setData(res);
}

return (
<div className="ml-5">
<p>testing data:</p>
<button onClick={giveItToMeAlready}>click to console.log data</button>
</div>
);
}
./src/app/page.tsx

"use client"
import { useState, useEffect } from 'react';
import { getAction } from './actions'

export const runtime = 'edge'
export default function Home() {

async function giveItToMeAlready() {
const res = await getAction();
console.log(res)
setData(res);
}

return (
<div className="ml-5">
<p>testing data:</p>
<button onClick={giveItToMeAlready}>click to console.log data</button>
</div>
);
}
The process of getting the data into your .tsx file was pretty poorly documented on the Cloudflare docs, but it could be assumed knowledge, and I'm pretty new to react and next.js so... @GM note that my instructions are for accessing data from a Cloudflare D1 instance, if you're using an external DB from somewhere else then this prob won't help you too much
thipperz
thipperz7mo ago
Data Fetching: Server Actions and Mutations | Next.js
Learn how to handle form submissions and data mutations with Next.js.
Want results from more Discord servers?
Add your server