react 및 react-query가 포함된 Pocketbase
포켓 베이스
오픈 소스 백엔드
차세대 SaaS 및 모바일 앱용
파일 1개
설정 문서
Download for Linux (11MB zip)
Download for Windows (11MB zip)
Download for macOS x64 (11MB zip)
Download for macOS ARM64 (11MB zip)
압축된 폴더를 다운로드하고 내용을 추출하면 바이너리가 명령줄에서 실행됩니다.
./pocketbase serve.
powershell에서는 다음과 같이 보일 것입니다.
.\pocketbase.exe serve
LAN에서 서비스를 제공하려면
윈도우에서 실행
ipconfig
그리고 리눅스에서 이와 같은 것
this doesn't work on mac or wsl, but there a lot of other ways
hostname -I
.\pocketbase.exe serve 192.168.20.87
일단 실행되면 ctrl + 터미널의 URL 중 하나를 클릭합니다.
Server started at: http://127.0.0.1:8090
- REST API: http://127.0.0.1:8090/api/
- Admin UI: http://127.0.0.1:8090/_/
관리자 대시보드
여기에서 새 컬렉션 생성 및 관리, 사용자, 로그 보기, 설정 변경 등 모든 작업을 수행할 수 있습니다.
다음으로 제공된 javascript sdk을 사용하여 front-edn을 처리합니다.
npm install pocketbase
그런 다음 config.ts 파일을 만듭니다(선택 사항, 구성 요소에 모든 논리를 넣을 수 있음).
import PocketBase, { Record } from 'pocketbase';
import { QueryClient } from "react-query";
export interface PeepResponse {
id: string;
created: string;
updated: string;
"@collectionId": string;
"@collectionName": string;
age: number;
bio: string;
name: string;
"@expand": {};
}
// export const client = new PocketBase("http://192.168.43.238:8090");
export const client = new PocketBase("http://127.0.0.1:8090");
export const realTime = async (
index: [string],
queryClient: QueryClient,
) => {
return await client.realtime.subscribe("peeps", function (e) {
console.log("real time peeps", e.record);
});
};
export const allPeeps=async():Promise<PeepResponse[]|Record[]>=>{
return await client.records.getFullList("peeps", 200 /* batch size */, {
sort: "-created",
});
}
위의 예에서 저는 peeps 컬렉션을 만들었고 여기에서 모든 레코드를 가져오는 함수allPeeps
가 있으며 클라이언트 SDK에도 페이지가 매겨진 변형이 있습니다.
질문
react-query와 함께 cpmponent에서 이것을 사용할 수 있습니다.
const peepsQuery = useQuery(["peeps"], allPeeps);
쿼리 내부의 데이터 배열을 매핑합니다.
peepQuery.data?.map((item)=>{
return <TheRows list={peepsQuery.data} />
})
돌연변이
sdk를 사용하여 새로운 엿보기를 추가할 수도 있습니다.
const mutation = useMutation(
({ data, index }: MutationVars) => {
return client.records.create(index, data);
},
// react-query options , in order to append new peeps by using the data returned after the mutation instaed of having to run the query again to update our list of peeps
// the index will be the react-query index and also the sdk client index
{
//print error if mutation fails
onError: (err, { data: newData, index }, context) => {
console.log("error saving the item === ", err)
},
//update the list with created record
onSuccess: (data, { index }) => {
console.log("vars === ", data, index)
queryClient.setQueryData(index, (old: any) => {
old.unshift(data);
return old;
});
console.log("successfull save of item ", data);
},
}
);
그런 다음 양식의 onsubmit 소품에 전달될 함수 내에서 이를 사용합니다.
const createPeep = async (data: any) => {
mutation.mutate({ data, index: "peeps" })
};
실시간 청취자
클라이언트 SDK는 컬렉션의 실시간 데이터를 지원합니다.
우리는 그것을 우리의 기능으로 감쌀 것입니다
export const realTime = async (
index: [string],
queryClient: QueryClient,
) => {
// sdk realtime listener
return await client.realtime.subscribe("peeps", function (e) {
console.log("real time peeps", e.record);
appendToCache(index,queryClient,e.record);
});
};
데이터를 직접 사용할 수 있지만 이미 react-query 관리 항목이 있으므로 기존['peeps']
쿼리 캐시에 새로운 변경 사항을 추가하는 것이 좋습니다.
export const appendToCache=async(index:[string],queryClient:QueryClient,newData:any)=>{
queryClient.setQueryData(index, (old:any) => {
old.unshift(newData)
return old
});
}
tip when using react-query QueryClient()
is that you should not do it like this
const queryClient = new QueryClient()
이렇게 하면 모든 구성 요소 렌더링에 새 인스턴스가 생성되기 때문입니다.
대신 제공된 후크를 사용하십시오.
const queryClient = usQueryClient()
QueryClient
의 현재 인스턴스를 반환합니다.
함수가 외부 파일에 있으면 소품으로 전달하십시오.
이제 앱 내에서 이것을 호출하면 데이터가 변경될 때 UI가 자동으로 업데이트됩니다.
입증
암호 1이 꽤 간단해 보였기 때문에 Oauth 공급자로 바로 건너뛰었습니다.
이 경우에는 이미 서비스 계정을 구성했기 때문에 Google 인증을 원했습니다.
클라이언트 ID와 클라이언트 암호가 필요합니다.
그런 다음 관리 대시보드에서 활성화합니다.
tips , when setting up the service account you'll need
allowed javascript origin
and a redirectUrl
you can use http://localhost:3000
and http://localhost:3000/redirect
respectively , this is assuming that's where your react app will be running
그 설정 후 로그인 페이지
import React from 'react'
import { TheButton } from '../Shared/TheButton';
import { client, providers } from './../../pocket/config';
interface LoginProps {
}
export const Login: React.FC<LoginProps> = ({}) => {
let provs = providers.authProviders
let redirectUrl = 'http://localhost:3000/redirect'
const startLogin = (prov:any)=>{
localStorage.setItem("provider", JSON.stringify(prov));
const url = provs[0].authUrl + redirectUrl
if (typeof window !== 'undefined') {
window.location.href = url;
}
}
return (
<div className='w-full h-full flex-col-center'>
<div className='text-3xl font-bold '>LOGIN</div>
{
provs&&provs?.map((item,index)=>{
return (
<TheButton
key={item.name}
label={item.name}
border={'1px solid'}
padding={'2%'}
textSize={'1.2 rem'}
onClick={()=>startLogin(item)}
/>
)
})
}
</div>
);
}
페이지를 리디렉션합니다.
import React from 'react'
import { useSearchParams,useNavigate,Navigate } from 'react-router-dom';
import { client } from './../../pocket/config';
import { useQueryClient } from 'react-query';
import { UserType } from './types';
interface RedirectProps {
user?: UserType | null
}
export const Redirect: React.FC<RedirectProps> = ({ user }) => {
//@ts-ignore
const local_prov = JSON.parse(localStorage.getItem('provider'))
const [searchParams] = useSearchParams();
const code = searchParams.get('code') as string
// compare the redirect's state param and the stored provider's one
const queryClient= useQueryClient()
let redirectUrl = 'http://localhost:3000/redirect'
const [loading,setLoading]= React.useState(true)
if (local_prov.state !== searchParams.get("state")) {
let url = 'http://localhost:3000/login'
if (typeof window !== 'undefined') {
window.location.href = url;
}
} else {
client.users.authViaOAuth2(
local_prov.name,
code,
local_prov.codeVerifier,
redirectUrl)
.then((response) => {
console.log("authentication data === ", response)
client.records.update('profiles', response.user.profile?.id as string, {
name:response.meta.name,
avatarUrl:response.meta.avatarUrl
}).then((res)=>{
console.log(" successfully updated profi;e",res)
}).catch((e) => {
console.log("error updating profile == ", e)
})
setLoading(false)
console.log("client modal after logg == ",client.authStore.model)
queryClient.setQueryData(['user'], client.authStore.model)
}).catch((e) => {
console.log("error logging in with provider == ", e)
})
}
if(user){
return <Navigate to="/" replace />;
}
return (
<div>
{
loading?(
<div className='w-full h-full flex-center'>loading .... </div>) :
(
<div className='w-full h-full flex-center'>success</div>
)}
</div>
);
}
이것은 또한 react-router-dom v6을 사용하고 있다고 가정합니다.
import './App.css'
import { useTheme } from './utils/hooks/themeHook'
import { BsSunFill, BsFillMoonFill } from "react-icons/bs";;
import { TheIcon } from './components/Shared/TheIcon';
import { Query, useQuery } from 'react-query';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Home } from './components/home/Home';
import { Redirect } from './components/login/Redirect';
import { Login } from './components/login/Login';
import { useEffect, useInsertionEffect } from 'react';
import { client } from './pocket/config';
import { ProtectedRoute } from './components/login/PrivateRoutes';
import { UserType } from './components/login/types';
type AppProps = {
// queryClient:QueryClient
};
type MutationVars = {
data: any;
index: string;
}
export interface FormOptions {
field_name: string;
field_type: string;
default_value: string | number
options?: { name: string; value: string }[]
}
function App({ }: AppProps) {
const { colorTheme, setTheme } = useTheme();
const mode = colorTheme === "light" ? BsSunFill : BsFillMoonFill;
const toggle = () => {
setTheme(colorTheme);
};
const getUser = async()=>{
return client.authStore.model
}
const userQuery = useQuery(["user"],getUser);
console.log("user query ====== ",userQuery)
if(userQuery.isLoading){
return(
<div className="w-full min-h-screen text-5xl font-bold flex-col-center">
LOADING....
</div>
)
}
if (userQuery.isError) {
return (
<div className="w-full min-h-screen text-5xl font-bold flex-col-center">
{/* @ts-ignore */}
{userQuery?.error?.message}
</div>
)
}
const user = userQuery.data as UserType|null|undefined
return (
<div
className="w-full min-h-screen flex-col-center scroll-bar
dark:bg-black dark:text-white "
>
<BrowserRouter>
<div className="fixed top-[0px] w-[100%] z-50">
<div className="w-fit p-1 flex-center">
<TheIcon Icon={mode} size={"25"} color={""} iconAction={toggle} />
</div>
</div>
<div className="w-full h-[90%] mt-16 ">
<Routes>
<Route
path="/"
element={
<ProtectedRoute user={user}>
<Home user={user} />
</ProtectedRoute>
}
/>
{/* @ts-ignore */}
<Route path="/login" element={<Login />} />
<Route path="/redirect" element={<Redirect user={user}/>} />
</Routes>
</div>
</BrowserRouter>
</div>
);
}
export default App
그리고 그게 다야 .
기존 앱을 pocketbase로 포팅하고 마주칠 수 있는 모든 별난 일에 대해 쓸 계획입니다.
전체 코드용github repo
지저분한 코드를 용서하십시오. 저는 코드를 작동시키는 데 더 집중하고 거기에 단어를 넣는 데 더 집중했습니다. 이 멋진 도구를 훨씬 쉽게 사용할 수 있기를 바랍니다.
Reference
이 문제에 관하여(react 및 react-query가 포함된 Pocketbase), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/tigawanna/pocketbase-with-react-and-react-query-40dn
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
./pocketbase serve.
.\pocketbase.exe serve
ipconfig
this doesn't work on mac or wsl, but there a lot of other ways
hostname -I
.\pocketbase.exe serve 192.168.20.87
Server started at: http://127.0.0.1:8090
- REST API: http://127.0.0.1:8090/api/
- Admin UI: http://127.0.0.1:8090/_/
npm install pocketbase
import PocketBase, { Record } from 'pocketbase';
import { QueryClient } from "react-query";
export interface PeepResponse {
id: string;
created: string;
updated: string;
"@collectionId": string;
"@collectionName": string;
age: number;
bio: string;
name: string;
"@expand": {};
}
// export const client = new PocketBase("http://192.168.43.238:8090");
export const client = new PocketBase("http://127.0.0.1:8090");
export const realTime = async (
index: [string],
queryClient: QueryClient,
) => {
return await client.realtime.subscribe("peeps", function (e) {
console.log("real time peeps", e.record);
});
};
export const allPeeps=async():Promise<PeepResponse[]|Record[]>=>{
return await client.records.getFullList("peeps", 200 /* batch size */, {
sort: "-created",
});
}
const peepsQuery = useQuery(["peeps"], allPeeps);
peepQuery.data?.map((item)=>{
return <TheRows list={peepsQuery.data} />
})
const mutation = useMutation(
({ data, index }: MutationVars) => {
return client.records.create(index, data);
},
// react-query options , in order to append new peeps by using the data returned after the mutation instaed of having to run the query again to update our list of peeps
// the index will be the react-query index and also the sdk client index
{
//print error if mutation fails
onError: (err, { data: newData, index }, context) => {
console.log("error saving the item === ", err)
},
//update the list with created record
onSuccess: (data, { index }) => {
console.log("vars === ", data, index)
queryClient.setQueryData(index, (old: any) => {
old.unshift(data);
return old;
});
console.log("successfull save of item ", data);
},
}
);
const createPeep = async (data: any) => {
mutation.mutate({ data, index: "peeps" })
};
export const realTime = async (
index: [string],
queryClient: QueryClient,
) => {
// sdk realtime listener
return await client.realtime.subscribe("peeps", function (e) {
console.log("real time peeps", e.record);
appendToCache(index,queryClient,e.record);
});
};
export const appendToCache=async(index:[string],queryClient:QueryClient,newData:any)=>{
queryClient.setQueryData(index, (old:any) => {
old.unshift(newData)
return old
});
}
tip when using react-query QueryClient()
is that you should not do it like this
const queryClient = new QueryClient()
const queryClient = usQueryClient()
암호 1이 꽤 간단해 보였기 때문에 Oauth 공급자로 바로 건너뛰었습니다.
이 경우에는 이미 서비스 계정을 구성했기 때문에 Google 인증을 원했습니다.
클라이언트 ID와 클라이언트 암호가 필요합니다.
그런 다음 관리 대시보드에서 활성화합니다.
tips , when setting up the service account you'll need
allowed javascript origin
and aredirectUrl
you can usehttp://localhost:3000
andhttp://localhost:3000/redirect
respectively , this is assuming that's where your react app will be running
그 설정 후 로그인 페이지
import React from 'react'
import { TheButton } from '../Shared/TheButton';
import { client, providers } from './../../pocket/config';
interface LoginProps {
}
export const Login: React.FC<LoginProps> = ({}) => {
let provs = providers.authProviders
let redirectUrl = 'http://localhost:3000/redirect'
const startLogin = (prov:any)=>{
localStorage.setItem("provider", JSON.stringify(prov));
const url = provs[0].authUrl + redirectUrl
if (typeof window !== 'undefined') {
window.location.href = url;
}
}
return (
<div className='w-full h-full flex-col-center'>
<div className='text-3xl font-bold '>LOGIN</div>
{
provs&&provs?.map((item,index)=>{
return (
<TheButton
key={item.name}
label={item.name}
border={'1px solid'}
padding={'2%'}
textSize={'1.2 rem'}
onClick={()=>startLogin(item)}
/>
)
})
}
</div>
);
}
페이지를 리디렉션합니다.
import React from 'react'
import { useSearchParams,useNavigate,Navigate } from 'react-router-dom';
import { client } from './../../pocket/config';
import { useQueryClient } from 'react-query';
import { UserType } from './types';
interface RedirectProps {
user?: UserType | null
}
export const Redirect: React.FC<RedirectProps> = ({ user }) => {
//@ts-ignore
const local_prov = JSON.parse(localStorage.getItem('provider'))
const [searchParams] = useSearchParams();
const code = searchParams.get('code') as string
// compare the redirect's state param and the stored provider's one
const queryClient= useQueryClient()
let redirectUrl = 'http://localhost:3000/redirect'
const [loading,setLoading]= React.useState(true)
if (local_prov.state !== searchParams.get("state")) {
let url = 'http://localhost:3000/login'
if (typeof window !== 'undefined') {
window.location.href = url;
}
} else {
client.users.authViaOAuth2(
local_prov.name,
code,
local_prov.codeVerifier,
redirectUrl)
.then((response) => {
console.log("authentication data === ", response)
client.records.update('profiles', response.user.profile?.id as string, {
name:response.meta.name,
avatarUrl:response.meta.avatarUrl
}).then((res)=>{
console.log(" successfully updated profi;e",res)
}).catch((e) => {
console.log("error updating profile == ", e)
})
setLoading(false)
console.log("client modal after logg == ",client.authStore.model)
queryClient.setQueryData(['user'], client.authStore.model)
}).catch((e) => {
console.log("error logging in with provider == ", e)
})
}
if(user){
return <Navigate to="/" replace />;
}
return (
<div>
{
loading?(
<div className='w-full h-full flex-center'>loading .... </div>) :
(
<div className='w-full h-full flex-center'>success</div>
)}
</div>
);
}
이것은 또한 react-router-dom v6을 사용하고 있다고 가정합니다.
import './App.css'
import { useTheme } from './utils/hooks/themeHook'
import { BsSunFill, BsFillMoonFill } from "react-icons/bs";;
import { TheIcon } from './components/Shared/TheIcon';
import { Query, useQuery } from 'react-query';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Home } from './components/home/Home';
import { Redirect } from './components/login/Redirect';
import { Login } from './components/login/Login';
import { useEffect, useInsertionEffect } from 'react';
import { client } from './pocket/config';
import { ProtectedRoute } from './components/login/PrivateRoutes';
import { UserType } from './components/login/types';
type AppProps = {
// queryClient:QueryClient
};
type MutationVars = {
data: any;
index: string;
}
export interface FormOptions {
field_name: string;
field_type: string;
default_value: string | number
options?: { name: string; value: string }[]
}
function App({ }: AppProps) {
const { colorTheme, setTheme } = useTheme();
const mode = colorTheme === "light" ? BsSunFill : BsFillMoonFill;
const toggle = () => {
setTheme(colorTheme);
};
const getUser = async()=>{
return client.authStore.model
}
const userQuery = useQuery(["user"],getUser);
console.log("user query ====== ",userQuery)
if(userQuery.isLoading){
return(
<div className="w-full min-h-screen text-5xl font-bold flex-col-center">
LOADING....
</div>
)
}
if (userQuery.isError) {
return (
<div className="w-full min-h-screen text-5xl font-bold flex-col-center">
{/* @ts-ignore */}
{userQuery?.error?.message}
</div>
)
}
const user = userQuery.data as UserType|null|undefined
return (
<div
className="w-full min-h-screen flex-col-center scroll-bar
dark:bg-black dark:text-white "
>
<BrowserRouter>
<div className="fixed top-[0px] w-[100%] z-50">
<div className="w-fit p-1 flex-center">
<TheIcon Icon={mode} size={"25"} color={""} iconAction={toggle} />
</div>
</div>
<div className="w-full h-[90%] mt-16 ">
<Routes>
<Route
path="/"
element={
<ProtectedRoute user={user}>
<Home user={user} />
</ProtectedRoute>
}
/>
{/* @ts-ignore */}
<Route path="/login" element={<Login />} />
<Route path="/redirect" element={<Redirect user={user}/>} />
</Routes>
</div>
</BrowserRouter>
</div>
);
}
export default App
그리고 그게 다야 .
기존 앱을 pocketbase로 포팅하고 마주칠 수 있는 모든 별난 일에 대해 쓸 계획입니다.
전체 코드용github repo
지저분한 코드를 용서하십시오. 저는 코드를 작동시키는 데 더 집중하고 거기에 단어를 넣는 데 더 집중했습니다. 이 멋진 도구를 훨씬 쉽게 사용할 수 있기를 바랍니다.
Reference
이 문제에 관하여(react 및 react-query가 포함된 Pocketbase), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/tigawanna/pocketbase-with-react-and-react-query-40dn텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)