hunk.code_window:           restore-keys: |
               ${{ runner.os }}-yarn-
               ${{ runner.os }}-
         - name: Install dependencies
           run: yarn --frozen-lockfile
  
hunk.labels: keep replace replace keep keep keep keep
hunk.after_edit:             ${{ runner.os }}-yarn-cache-
  
hunk.file_path: .github/workflows/test.yml
hunk.type: replace
file: # v0.8.0 Release Notes
  
  This release is pretty significant and we're really excited about it. It sets the foundation for everything else we want to do with Remix. Hang on tight though, there are a lot of changes, and we appreciate your patience as we shift the API around a bit during this preview period. After our production release we'll have proper, backward-compatible API deprecation, but right now we're prioritizing getting Remix stable.
  
  We recommend [running through the tutorial](https://remix.run/dashboard/docs/tutorial/installation) with fresh eyes again to capture all of these changes.
  
  ## Improvements
  
  ### `<Form>` component and Actions
  
  While previously "loaders" allowed you to load route data, "Actions" coupled with `<Form>` allow you to make changes to data with the simplicity of old-school forms posts but the progressive enhancement of React.
  
  ```tsx
  <Form method="post" action="/projects">
    <p>
      <label>
        Name: <input name="name" type="text" />
      </label>
    </p>
    <p>
      <label>
        Description: <textarea name="description" />
      </label>
    </p>
    <p>
      <button type="submit">Create</button>
    </p>
  </Form>
  ```
  
  And the action that handles the post:
  
  ```ts
  import type { Action } from "@remix-run/data";
  import { parseFormBody, redirect } from "@remix-run/data";
  
  let action: Action = async ({ request }) => {
    let newProject = parseFormBody(request);
    let project = await createProject(newProject);
    return redirect(`/projects/${project.id}`);
  };
  
  export { action };
  ```
  
  Finally, you can make the interaction fancy with `usePendingFormSubmit()` for loading indication and optimistic UI:
  
  ```tsx
  import { usePendingFormSubmit } from "@remix-run/react";
  
  function SomePage() {
    let pendingSubmit = usePendingFormSubmit();
    if (pendingSubmit) {
      return (
        <div style={{ opacity: 0.5 }}>{pendingSubmit.data.get("title")}</div>
      );
    } else {
      return (
        <Form>
          <input name="title" />
          <button type="submit">Create</button>
        </Form>
      );
    }
  }
  ```
  
  Read more:
  
  - [Mutations](https://remix.run/dashboard/docs/mutations)
  - [Actions and Loaders](https://remix.run/dashboard/docs/data)
  - [Form](https://remix.run/dashboard/docs/react#form)
  - [usePendingFormSubmit](https://remix.run/dashboard/docs/react#usePendingFormSubmit)
  
  ### Added `usePendingLocation`
  
  This hook gives you the next location so you can match on its pathname for contextual loading indication on links and more
  
  ```tsx
  let nextLocation = usePendingLocation();
  console.log(nextLocation && nextLocation.pathname);
  ```
  
  Read More:
  
  - [usePendingLocation](https://remix.run/dashboard/docs/react#usependinglocation)
  - [history location interface](https://github.com/ReactTraining/history/blob/master/docs/api-reference.md#location)
  
  ### Added `parseFormBody`
  
  Now that we have `<Form>` and Actions, you need a way to parse the form's request body.
  
  ```ts
  import { parseFromBody } from "@remix-run/data";
  
  let action = ({ request }) => {
    let body = parseFormBody(request);
  };
  ```
  
  It returns a `URLSearchParams` or `FormBody` depending on the `encType` of the form, both objects work almost identically though:
  
  Read more:
  
  - [parseFormBody](https://remix.run/dashboard/docs/data#parseFormBody)
  - [FormData on MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
  - [URLSearchParams on MDN](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
  
  ### Request object passed to loaders and actions
  
  Instead of passing a URL, we pass the whole Request object so you can read the method, parse the body, etc.
  
  ```ts
  let loader = ({ request }) => {
    request.method;
    request.url;
  
    let url = new URL(request.url);
    url.get("some-param");
    // etc.
  };
  ```
  
  ### Sessions
  
  Remix platform wrappers like `@remix-run/express` can detect when you've enabled sessions for your app and automatically send a remix `session` object to loaders and actions. This is great for storing flash messages about actions that just happened on the server across your app or storing form validation errors to display on the next page.
  
  ```ts
  let action = async ({ params, session }) => {
    let deletedProject = await archiveProject(params.projectId);
    session.flash(
      "globalMessage",
      `Project ${deletedProject.name} successfully archived`
    );
    return redirect("/dashboard");
  };
  ```
  
  ```ts
  // data/global.ts
  let loader = ({ session }) => {
    let message = session.get("globalMessage") || null;
    return { message };
  };
  ```
  
  ```tsx
  // app/App.tsx
  export default function App() {
    let { message } = useGlobalData();
    return (
      <html>
        <head>
          <Meta />
          <Styles />
        </head>
        <body>
          {message && <div>{message}</div>}
          <Routes />
          <Scripts />
        </body>
      </html>
    );
  }
  ```
  
  Read More:
  
  - [Sessions](https://remix.run/dashboard/docs/sessions)
  
  ### Importing `.json` files
  
  You can now import `.json` files just like in Node.
  
  ```tsx
  import json from "./something.json";
  
  console.log(json);
  ```
  
  ### `.ts` and `.tsx` for `routes/404` and `routes/500`
  
  Previously they had to be `.js`.
  
  ## Breaking Changes
  
  ### Renamed `@remix-run/loaders` to `@remix-run/data`
  
  Also, the remix config name for your loaders changed from `loadersDirectory` to `dataDirectory`.
  
  While this is configurable, we also changed the default folder from "loaders" to "data" in the starter templates, and all docs now talk about the "data" folder instead of "loaders".
  
  We made this change because your data loaders can now export two functions: `loader` and `action`. So it didn't make sense to call them "loaders" anymore but "data modules". So a "data module" can export a "loader" and and "action". Data modules live in `data/routes/**/*{.js,.ts}`.
  
  In your data modules (previously "loaders"):
  
  ```ts
  // old
  import { json } from "@remix-run/loader";
  
  // new
  import { json } from "@remix-run/data";
  ```
  
  In your `remix.config.js`
  
  ```js
  // old
  exports.loadersDirectory = "./loaders";
  
  // new
  exports.dataDirectory = "./loaders";
  
  // or if you want to be more semantic with the changes here, just make sure to
  // rename the folder!
  exports.dataDirectory = "./data";
  ```
  
  ### Removed `@remix-run/notFound`
  
  It wasn't that helpful and gave people the wrong idea.
  
  ```ts
  // old:
  import { notFound } from "@remix-run/loader";
  
  module.exports = () => {
    return notFound();
  };
  
  // new:
  exports.loader = () => {
    return new Response("", { status: 404 });
  };
  ```
  
  Please note that this does not render the `routes/404` component, it renders whatever matched so you'll probably want to send some extra information down so your UI can handle it better.
  
  ```ts
  // old:
  import { notFound } from "@remix-run/loader";
  
  module.exports = () => {
    return notFound();
  };
  
  // new:
  import { json } from "@remix-run/loader";
  exports.loader = () => {
    return json({ notFound: true }, { status: 404 });
  };
  ```
  
  Then you can read that data from `useRouteData()` and render a contextual not found page with the matching component.
  
  ### Removed loader `url`
  
  You can use the request object to create a url:
  
  ```ts
  // old
  let loader = ({ url }) => {
    let param = url.searchParams.get("foo");
  };
  
  // new
  let loader = ({ request }) => {
    let url = new URL(request.url);
    let param = url.searchParams.get("foo");
  };
  ```
  
  ### Removed `useLocationPending` in favor of `usePendingLocation`
  
  ```tsx
  // old
  let pending = useLocationPending();
  
  // new
  let nextLocation = usePendingLocation();
  
  // or coerce to boolean and ensure identical behavior to your old code:
  let nextLocation = usePendingLocation();
  let pending = !!nextLocation;
  ```
  