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
thipperz5mo 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_witt5mo ago
No description
real_witt
real_witt5mo 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
thipperz5mo 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_witt5mo ago
Ahh ok I'm gonna try this, thanks
thipperz
thipperz5mo ago
Let me know if it works for you
real_witt
real_witt5mo 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
thipperz5mo ago
The data that you return from a server action must be serializable - what are u returning from your GET function?
real_witt
real_witt5mo 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
thipperz5mo 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_witt5mo ago
oh let's go
No description
real_witt
real_witt5mo 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
thipperz5mo 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_witt5mo 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
thipperz5mo 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 (?)
Want results from more Discord servers?
Add your server