logo
  • Guide
  • Config
  • Plugin
  • API
  • Examples
  • Community
  • Modern.js 2.x Docs
  • English
    • 简体中文
    • English
    • Start
      Introduction
      Quick Start
      Upgrading
      Glossary
      Tech Stack
      Core Concept
      Page Entry
      Build Engine
      Web Server
      Basic Features
      Routes
      Routing
      Config Routes
      Data Solution
      Data Fetching
      Data Writing
      Data Caching
      Rendering
      Server-Side Rendering
      Streaming SSR
      Rendering Cache
      Static Site Generation
      Render Preprocessing
      Styling
      Styling
      Use CSS Modules
      Using CSS-in-JS
      Using Tailwind CSS
      HTML Template
      Import Static Assets
      Import JSON Files
      Import SVG Assets
      Import Wasm Assets
      Debug
      Data Mocking
      Network Proxy
      Using Rsdoctor
      Using Storybook
      Testing
      Playwright
      Vitest
      Jest
      Cypress
      Path Alias
      Environment Variables
      Output Files
      Deploy Application
      Advanced Features
      Using Rspack
      Using BFF
      Basic Usage
      Runtime Framework
      Extend BFF Server
      Extend Request SDK
      File Upload
      Cross-Project Invocation
      Optimize Page Performance
      Code Splitting
      Inline Static Assets
      Bundle Size Optimization
      React Compiler
      Improve Build Performance
      Browser Compatibility
      Low-Level Tools
      Source Code Build Mode
      Server Monitor
      Monitors
      Logs Events
      Metrics Events
      Internationalization
      Basic Concepts
      Quick Start
      Configuration
      Locale Detection
      Resource Loading
      Routing Integration
      API Reference
      Advanced Usage
      Best Practices
      Custom Web Server
      Topic Detail
      Module Federation
      Introduction
      Getting Started
      Application-Level Modules
      Server-Side Rendering
      Deployment
      Integrating Internationalization
      FAQ
      Dependencies FAQ
      CLI FAQ
      Build FAQ
      HMR FAQ
      Deprecated
      📝 Edit this page
      Previous pageData FetchingNext pageData Caching

      #Data Writing

      In the Data Fetching section, we introduced how Modern.js fetches data. This might bring up two questions:

      1. How do I update the data returned by the Data Loader?
      2. How do I send new data to the server?

      In Modern.js, you can use Data Action to address these scenarios.

      #What is Data Action

      Modern.js recommends managing routes using conventional routing. Each route component (layout.ts, page.ts, or $.tsx) can have a same-named .data file. These files can export an action function, known as Data Action, which developers can call at an appropriate time:

      .
      └── routes
          └── user
              ├── layout.tsx
              └── layout.data.ts

      You can export a Data Action function in the routes/user/layout.data.ts file:

      routes/user/layout.data.ts
      import type { ActionFunction } from '@modern-js/runtime/router;
      
      export const action: ActionFunction = ({ request }) => {
        const newUser = await request.json();
        const name = newUser.name;
        return updateUserProfile(name);
      }

      In the route component, you can use useFetcher to get the corresponding submit function, which executes and triggers Data Action:

      routes/user/layout.tsx
      import {
        useFetcher,
        useLoaderData,
        useParams,
        Outlet
      } from '@modern-js/runtime/router';
      
      export default () => {
        const userInfo = useLoaderData();
        const { submit } = useFetcher();
        const editUser = () => {
          const newUser = {
            name: 'Modern.js'
          }
          return submit(newUser, {
            method: 'post',
            encType: 'application/json',
          })
        }
        return (
          <div>
            <button onClick={editUser}>edit user</button>
            <div className="user-profile">
              {userInfo}
            </div>
            <Outlet context={userInfo}></Outlet>
          </div>
        )
      }

      After executing submit, the defined action function will be triggered. You can get the submitted data from the parameters in the action function and ultimately send the data to the server.

      Once the action function executes, Modern.js will automatically run the loader function code to update the data and view accordingly.

      action flow

      #action Function

      Similar to the loader function, the action function takes two parameters: params and request.

      #params

      params is the dynamic route segments when the route is a dynamic route, which are passed as parameters to the action function:

      routes/user/[id]/page.data.ts
      import { ActionFunctionArgs } from '@modern-js/runtime/router;
      
      // When visiting /user/123, the function parameter is `{ params: { id: '123' } }`
      export const action = async ({ params }: ActionFunctionArgs) => {
        const { id } = params;
        const res = await fetch(`https://api/user/${id}`);
        return res.json();
      };

      #request

      request is an instance of Fetch Request.

      Using request, you can retrieve data submitted from the client in the action function, such as request.json(), request.formData(), request.json(), etc. Refer to Data Types for which API to use.

      routes/user/[id]/page.data.ts
      import { ActionFunctionArgs } from '@modern-js/runtime/router';
      
      export const action = async ({ request }: ActionFunctionArgs) => {
        const newUser = await request.json();
        return updateUser(newUser);
      };

      #Return Value

      The return value of the action function can be any serializable content or a Fetch Response instance. You can access the content via useActionData.

      #useSubmit and useFetcher

      #Difference

      You can call Data Action using useSubmit or useFetcher. The difference is that useSubmit triggers browser navigation, while useFetcher does not.

      useSubmit:

      const submit = useSubmit();
      submit(null, { method: "post", action: "/logout" });

      useFetcher:

      const { submit } = useFetcher();
      submit(null, { method: "post", action: "/logout" });

      The submit function takes two parameters. The first parameter is the formData passed to the server. The second parameter is an optional object where:

      • method corresponds to the form submission method. In most data writing scenarios, you can set the method to post.
      • action specifies the route component to trigger the Data Action. If not provided, it defaults to the current route component's action. For example, executing submit in user/page.tsx or its sub-components will trigger the action defined in user/page.data.ts.
      Info

      More information about the API can be found in the relevant documentation: useSubmit, useFetcher.

      #Data Types

      The first parameter of the submit function can accept different types of values.

      For example, FormData:

      let formData = new FormData();
      formData.append("cheese", "gouda");
      submit(formData);
      // In the action, you can get the data by request.json

      or URLSearchParams:

      let searchParams = new URLSearchParams();
      searchParams.append("cheese", "gouda");
      submit(searchParams);
      // In the action, you can get the data by request.json

      or any value that the URLSearchParams constructor accepts:

      submit("cheese=gouda&toasted=yes");
      submit([
        ["cheese", "gouda"],
        ["toasted", "yes"],
      ]);
      // In the action, you can get the data by request.json

      By default, if the first parameter of the submit function is an object, the corresponding data will be encoded as formData:

      submit(
        { key: "value" },
        {
          method: "post",
          encType: "application/x-www-form-urlencoded",
        }
      );
      
      // In the action, you can get the data by request.formData

      You can also specify the encoding type via the second parameter:

      submit(
        { key: "value" },
        { method: "post", encType: "application/json" }
      );
      
      submit('{"key":"value"}', {
        method: "post",
        encType: "application/json",
      });
      
      // In the action, you can get the data by request.json

      or submit plain text:

      submit("value", { method: "post", encType: "text/plain" });
      // In the action, you can get the data by request.text

      #Why Data Action?

      Modern.js provides Data Action primarily to keep the UI and server state in sync, reducing the burden of state management. Traditional state management methods maintain state separately on the client and remote server:

      traditional state manage

      In Modern.js, we aim to help developers automatically synchronize client and server state using Loader and Action:

      state manage

      If the shared data in your project mainly comes from the server, you don't need to introduce a client-side state management library. Use Data Loader to request data and share it in sub-components via useRouteLoaderData. Use Data Action to modify and synchronize the server's state.

      #CSR and SSR

      Similar to Data Loader, in SSR projects, Data Action is executed on the server (the framework automatically sends requests to trigger Data Action), while in CSR projects, Data Action is executed on the client.