Giter VIP home page Giter VIP logo

next-fetch's Introduction

Next Fetch

Think in React, instead about routing: Next Fetch is an intuitive way to dynamically fetch data from API endpoints in Next.js, using your favorite libraries.

πŸ’ƒ Import your API endpoints instead of making a stringified dance

πŸ”’ Infer the types end-to-end for your data based on its implementation

βš› Think in React, instead of routing: you only export a React hook!

πŸ•΅ Embrace best-practices: input validation, error handling, etc.

🌐 Use Request and Response classes as building blocks, no matter what runtime you're running on (Node.js or Edge)

πŸ“ Use <Form /> component for making progressive enhanced experiences

🀯 Supports SWR and React Query out of the box!

What does that mean?

Next Fetch is using compile-time transformations to allow you to import your API endpoints instead of referencing them as plain strings, while keeping the type definitions co-located with the implementation.

Next.js + Next Fetch Plain Next.js
API implementation
// pages/api/message.swr.tsx
import { query } from "@next-fetch/swr";
import z from "zod";

export const useMessage = query(
  // use zod for input validation
  z.object({
    name: z.string(),
  }),
  async function ({ name }) {
    // this.request is a `Request` instance
    return { hello: `world, ${name}` };
  }
);
// pages/api/message.tsx
import type { NextApiRequest, NextApiResponse } from "next";

export default (req: NextApiRequest, res: NextApiResponse) => {
  // ad-hoc input validation
  const name = Array.isArray(req.query.name)
    ? req.query.name[0]
    : req.query.name;
  if (!name) {
    return res.status(400).json({ error: "No name provided" });
  }

  // explicit type defniitions required
  return res.status(200).json({ hello: `world, ${name}` });
};
API consumption
import { useMessage } from "./api/message";

export default function MyPage() {
  const { data, error } = useMessage({
    // will autocomplete and
    // type-check the input arguments
    name: "John Doe",
  });

  // autocompletes and type-checks!
  const hello = data?.hello;

  return <div>{/* ... */}</div>;
}
// pages/index.tsx
import useSWR from "swr";

const fetcher = (url: string) => {
  const response = await fetch(url);

  // handle input validation or runtime errors
  if (!response.ok) {
    const text = await response.text().catch(() => null);
    throw new Error(`response is not okay${text ? `: ${text}` : ""}`);
  }

  return await response.json();
};

export default function MyPage() {
  const { data, error } = useSWR("/api/message?name=John%20Doe", fetcher);
  /** `data` is `any`, so we need to explicitly type-cast here */
  return <div>{/* ... */}</div>;
}

next-fetch's People

Contributors

charlesfrisbee avatar filipo11021 avatar github-actions[bot] avatar handtrix avatar mmyoji avatar mwarger avatar nurodev avatar schniz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

next-fetch's Issues

Allow disabling the fetching based on a condition

The SWR API allows users to opt out of fetching by having a null key:

useSWR(() => someCondition ? null : "/my/key", ...)

I think we can support that too with our API. We can accept null and/or a function that resolves into a null/object to allow conditional fetching

RFC: multipart/form-data

Summary

Currently the Form.encType has no effect when using @next-fetch/swr/form.
Enabling encTypes like multipart/form-data would allow all kinds of common features like File Uploads.

Basic example

File: pages/file-upload.tsx

export default function MyImageUpload() {
  const mutation = useImageUpload();
  return (
    <Form encType="multipart/form-data" mutation={mutation}>
      <input type="file" name="someFile" accept="image/*" />
      <button type="submit">Submit</button>
    </Form>
  );
}

File: pages/api/image.swr.ts

import { zfd } from "zod-form-data";

export const useImageUpload = mutation(
  zfd.formData({ someFile: zfd.file() }),
  async (formData) => {
    const file: File = formData.get('someFile')
    // ...
    return { message: "Success" };
  }
);

Motivation

Today next-fetch only supports application/x-www-form-urlencoded forms. These forms are quite limited, since they only allow structured string data.

Adding multipart/form-data support, would allow sending all kind of hybrid data (strings, blobs, etc) to the server while still being able to support native html forms (hookResponse).

Challenges

  • FormData inside Edge Runtime and Node.js still lacks in some features.
  • FormData blobs need to be lazy on the server.
  • zod currently doesn't support ES Maps afaik. Nevertheless zod-form-data can help here.
  • ...to be continued

Adoption strategy

The type definition of @next-fetch/swr/form element should be updated for the time multipart/form-data is not supported, otherwise a breaking change will be needed when the feature has been implemented.

export function Form<Data, Error>({
  mutation,
  mutationConfig,
  ...props
}: React.HTMLProps<HTMLFormElement> &
  React.PropsWithChildren<{
    mutation: HookWithFormSubmission<Data, Error>;
    mutationConfig?: SWRMutationConfiguration<Data, Error>;
+    encType?: 'application/x-www-form-urlencoded'
  }>) {
  const { formProps } = useForm(mutation, mutationConfig);
  return createElement("form", { ...formProps, ...props }, props.children);
}

Later this feature can be allowed by adding multipart/form-data to the encType union.

export function Form<Data, Error>({
  mutation,
  mutationConfig,
  ...props
}: React.HTMLProps<HTMLFormElement> &
  React.PropsWithChildren<{
    mutation: HookWithFormSubmission<Data, Error>;
    mutationConfig?: SWRMutationConfiguration<Data, Error>;
-    encType?: 'application/x-www-form-urlencoded' 
+    encType?: 'application/x-www-form-urlencoded' | 'multipart/form-data'
  }>) {
  const { formProps } = useForm(mutation, mutationConfig);
  return createElement("form", { ...formProps, ...props }, props.children);
}

Links

Increase typesafety for mutations

# Problem

When submitting this mutation, an Unhandled Runtime Error is thrown. next-fetch should let us know there’s not an input with name="content"

// pages/api/todos.swr.ts
export const useCreateTodo = mutation(
  z.object({
    content: z.string(),
  }),
  async ({ content }) => {
    // ...
  }
);
// pages/index.tsx

export default function Home() {
  const mutation = useCreateTodo();
  
  <Form mutation={mutation}>
      <label htmlFor='newTodo'>Add a new todo</label>
      <input
          type='text'
          name='newTodo'
          id='newTodo'			
          placeholder='New todo'
        />
      <button type='submit'>Add todo</button>
  </Form>
}

Solution Description

When using the <Form /> component, there should be typesafety letting the user know they don't have an <input /> that aligns with their mutation's input.

I'd expect the same typesafety if programmatically calling the mutation like so:

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  const values = new FormData(formRef.current as HTMLFormElement);
  const data = Object.fromEntries(values);
  createTodo.trigger(data);
};

CleanShot 2022-09-02 at 15 21 07@2x

This is the error on data being passed to trigger

feat: Conditional Fetching

Currently there is no declarative way to conditionally fetch data.

Future

This is how an implementation could look like following the swr convention.

import { useTestQuery } from "./api/test.swr";

export default function IndexPage({ id }) {
  const testQuery = useTestQuery(id ? { id } : null);
  return (
    ...
  );
}

fetch wrongly parses absolute URL when concatenating with .env variable

fetch when parsing the received URL are adding unexpected resources, making the request to fail with HTTP 404

// .env.local
BASE_FETCH_URL=http://localhost:3000

by using like this:
process.env.BASE_FETCH_URL + '/api/apply/get-session-user'

results in the following URL:
http://localhost:3000/api/apply/get-session-user

This URL are getting well concatenated (also using template literals). You can confirm it by checking the console.log present into the Screenshot below
But into the browser, it's getting requested with "extra" resources right after the top-level domain (or the server port in my case)
Request URL: http://localhost:3000/apply/undefined/api/apply/get-session-user
Look at the first resource /apply/undefined. This resource shouldn't be present since it wasn't part of the original URL

Detail... It works by hard-coding the URL and eventually works with the relative resource path

Summarizing:

  • It works: await fetch('http://localhost:3000/api/apply/get-session-user')

  • It works: await fetch('http://localhost:3000' + '/api/apply/get-session-user')

  • It works sometimes: await fetch('/api/apply/get-session-user')

  • It doesn't works: await fetch(process.env.BASE_FETCH_URL + '/api/apply/get-session-user')

image

SSR support?

@Schniz Gal! Does this support SSR? If it doesn't, can it? I ask because I heavily use tRPC and can get SSR done with it.

If we can get this to do SSR, I would absolutely switch to this and make tons of contributions to make us a documentation site and everything. This could be a huge addition for the community!

feat: support context builder?

Thanks for the phenomenal work. Having used trpc, I can't wait to have a native nextjs solution.

Something that works well in trpc is the notion of a context builder. We can build the authentication state for instance and provide the current user for all query mutation automatically.

I know we could do that on every query mutation manually / and or with an Higher Order Function but I am wondering if there is any plan to have that in scope.

How can I help ?

Allow defining an endpoint without a server-side runtime parsing

When I asked for feedback, one person told me he does not like the fact this library forces you to declare a type in advance using zod, alternative libraries, or pass any object with the function parse.

So their idea was:

export const useMyHook = query((value: { untrusted: string }) => {
  return "Hello, world!"
});

I am not so sure about it because I think we should have good defaults and suggest better and safer code. Maybe we can do something like:

import { unsafe } from 'next-swr-endpoints';

export const useMyHook = query(unsafe, (value: { untrusted: string }) => {
 ...
}));

// or as a function, because then I think that we can declare it as `unsafe<T>(): T`
export const useMyHook = query(unsafe(), (value: { untrusted: string }) => {
 ...
}));

API routes inside `/src/pages/api/*` do not work

This is a mind blowing project and I was just changing the code for my site. And I have my API routes and everything in the src folder.

But instead of sending request to /api/whatever it sends request to /src/pages/api/whatever
image

Is there a configuration I have to do if the pages dir is in src

feat: expose a req/res context for API implementation

Currently I'm unable to authenticate user requests since doing so with NextAuth requires the Request and Response objects to check auth.

I'd like to propose an API similar to this, exposing a ctx object:

// pages/api/message.swr.tsx
import { query } from "@next-fetch/swr";
import z from "zod";

import { authOptions } from 'pages/api/auth/[...nextauth]';
import { unstable_getServerSession } from "next-auth/next";

export const useMessage = query(
  async function ({ ctx }) {
    const session = await unstable_getServerSession(ctx.req, ctx.res, authOptions)
    return { hello: `world, ${session.user.name}` };
  }
);

Additionally, the documentation is misleading:

export const useMessage = query(
  // use zod or other input validation libraries
  z.object({
    name: z.string(),
  }),
  async function ({ name }) {
    // this.request is a `Request` instance            <------------ this.request is not available here
    return { hello: "world, " + name };
  }
);

Allow defining an endpoint with client-side response validation

The idea behind this feature is to enable long-running clients to react to changes in the backend schemas. Imagine you have an endpoint that returns { version: 1, data: string } and then while the customer is on your app, the server redeploys and now it returns { version: 2, result: string }. You probably want to make a hard-refresh, or at least show an error.

The suggested API would be something like:

import { query } from 'next-swr-endpoints';
export const useNoClientTypecheck = query(myParser, (value) => ...)
export const useWithClientTypecheck = query({ input: myParser, output: anotherParser }, value => ...)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.