npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

frappe-react-sdk

v1.9.0

Published

React hooks for Frappe Framework

Downloads

3,839

Readme

frappe-react-sdk

React hooks library for a Frappe Framework backend.

Features

The library currently supports the following features:

  • 🔐 Authentication - login with username and password (cookie based) + token based authentication, logout and maintain user state
  • 🗄 Database - Hooks to get document, get list of documents, get count, create, update and delete documents
  • 📄 File upload - Hook to upload a file to the Frappe filesystem. Maintains loading, progress and error states.
  • 🤙🏻 API calls - Hooks to make API calls to your whitelisted backend functions and maintain state
  • 🔍 Search - Hook to search documents in your database (with debouncing ✨)

We plan to add the following features in the future:

  • Support for other common functions like exists in the database.

The library uses frappe-js-sdk and SWR under the hood to make API calls to your Frappe backend.

SWR

SWR uses a cache invalidation strategy and also updates the data constantly and automatically (in the background). This allows the UI to always be fast and reactive. The hooks in the library use the default configuration for useSWR but you will be able to overwrite the configuration of useSWR. Please refer to the useSWR API Options

Looking for a Frappe frontend library for other Javascript frameworks?

You can use frappe-js-sdk to interface your frontend web app with Frappe.

Maintainers

| Maintainer | GitHub | Social | | -------------- | ----------------------------------------------- | ------------------------------------------------------------ | | Nikhil Kothari | nikkothari22 | @nik_kothari22 | | Janhvi Patil | janhvipatil | @janhvipatil_ | | Sumit Jain | sumitjain236 | LinkedIn |

Installation

npm install frappe-react-sdk

or

yarn add frappe-react-sdk

Note - All examples below are in Typescript. If you want to use it with Javascript, just ignore the type generics like <T> in the examples below.

Initialising the library

To get started, initialise the library by wrapping your App with the FrappeProvider. You can optionally provide the URL of your Frappe server if the web app is not hosted on the same URL.

In App.tsx or App.jsx:

import { FrappeProvider } from "frappe-react-sdk";

function App() {
    /** The URL is an optional parameter. Only use it if the Frappe server is hosted on a separate URL **/
  return (
    <FrappeProvider url='https://my-frappe-server.frappe.cloud'>
    {/** Your other app components **/}
    </FrappeProvider>
  )

In case you want to use the library with token based authentication (OAuth bearer tokens or API key/secret pairs), you can initialise the library like this:

import { FrappeProvider } from 'frappe-react-sdk';

function App() {

  /** The URL is an optional parameter. Only use it if the Frappe server is hosted on a separate URL **/
  return (
    <FrappeProvider url='https://my-frappe-server.frappe.cloud' tokenParams={
        useToken: true,
        // Pass a custom function that returns the token as a string - this could be fetched from LocalStorage or auth providers like Firebase, Auth0 etc.
        token: getTokenFromLocalStorage(),
        // This can be "Bearer" or "token"
        type: "Bearer"
    } >
      {/** Your other app components **/}
    </FrappeProvider>
  );
}

Authentication

The useFrappeAuth hook allows you to maintain the state of the current user, as well as login and logout the current user.

The hook uses useSWR under the hood to make the get_current_user API call - you can also pass in parameters to configure the behaviour of the useSWR hook.

export const MyAuthComponent = () => {
  const {
    currentUser,
    isValidating,
    isLoading,
    login,
    logout,
    error,
    updateCurrentUser,
    getUserCookie,
  } = useFrappeAuth();

  if (isLoading) return <div>loading...</div>;

  // render user
  return (
    <div>
      {currentUser}
      <button onClick={() => login('administrator', 'admin')}>Login</button>
      <button onClick={logout}>Logout</button>
      <button onClick={updateCurrentUser}>Fetch current user</button>
    </div>
  );
};

The hook will not make an API call if no cookie is found. If there is a cookie, it will call the frappe.auth.get_logged_user method. The hook will throw an error if the API call to frappe.auth.get_logged_user fails (network issue etc) or if the user is logged out (403 Forbidden). Handle errors accordingly and route the user to your login page if the error is because the user is not logged in.

The getUserCookie method can be used to reset the auth state if you encounter an authorization error in any other API call. This will then reset the currentUser to null.

Database

Fetch a document

The useFrappeGetDoc hook can be used to fetch a document from the database. The hook uses useSWR under the hood and it's configuration can be passed to it.

Parameters:

| No. | Variable | type | Required | Description | | --- | --------- | ------------------ | -------- | ------------------------- | | 1. | doctype | string | ✅ | Name of the doctype | | 2. | docname | string | ✅ | Name of the document | | 3. | options | SWRConfiguration | - | SWR Configuration Options |

export const MyDocumentData = () => {
  const { data, error, isValidating, mutate } = useFrappeGetDoc<T>(
    'User',
    'Administrator',
    {
      /** SWR Configuration Options - Optional **/
    }
  );

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return (
      <p>
        {JSON.stringify(data)}
        <button onClick={() => mutate()}>Reload</button>
      </p>
    );
  }
  return null;
};

Fetch list of documents

The useFrappeGetDocList hook can be used to fetch a list of documents from the database.

Parameters:

| No. | Variable | type | Required | Description | | --- | --------- | ------------------ | -------- | --------------------------------------------------------------------------------------------------- | | 1. | doctype | string | ✅ | Name of the doctype | | 2. | args | GetDocListArgs | - | optional parameter (object) to sort, filter, paginate and select the fields that you want to fetch. | | 3. | options | SWRConfiguration | - | SWR Configuration Options |

export const MyDocumentList = () => {
  const { data, error, isValidating, mutate } = useFrappeGetDocList<T>(
    'DocType',
    {
      /** Fields to be fetched - Optional */
      fields: ['name', 'creation'],
      /** Filters to be applied - SQL AND operation */
      filters: [['creation', '>', '2021-10-09']],
      /** Filters to be applied - SQL OR operation */
      orFilters: [],
      /** Fetch from nth document in filtered and sorted list. Used for pagination  */
      limit_start: 5,
      /** Number of documents to be fetched. Default is 20  */
      limit: 10,
      /** Sort results by field and order  */
      orderBy: {
        field: 'creation',
        order: 'desc',
      },
      /** Fetch documents as a dictionary */
      asDict: false,
    },
    {
      /** SWR Configuration Options - Optional **/
    }
  );

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return (
      <p>
        {JSON.stringify(data)}
        <button onClick={() => mutate()}>Reload</button>
      </p>
    );
  }
  return null;
};

Type declarations are available for the second argument and will be shown to you in your code editor.

Some other simpler examples (click to expand):

In this case, only the name attribute will be fetched.

export const MyDocumentList = () => {
  const { data, error, isValidating } = useFrappeGetDocList<string>('User');

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return (
      <ul>
        {data.map((username) => (
          <li>{username}</li>
        ))}
      </ul>
    );
  }
  return null;
};
type UserItem = {
    name: string,
    email: string
}
export const MyDocumentList = () => {
    const [pageIndex, setPageIndex] = useState(0)
    const { data, error, isValidating } = useFrappeGetDocList<UserItem>("User" , {
        fields: ["name", "email"],
        limit_start: pageIndex,
        /** Number of documents to be fetched. Default is 20  */
        limit: 10,
        /** Sort results by field and order  */
        orderBy: {
            field: "creation",
            order: 'desc'
        }
    });

    if (isValidating) {
        return <>Loading</>
    }
    if (error) {
        return <>{JSON.stringify(error)}</>
    }
    if (data) {
        return <div>
            <ul>
            {
                data.map({name, email} => <li>{name} - {email}</li>)
            }
            </ul>
            <button onClick={() => setPageIndex(pageIndex + 10)}>Next page</button>
        </div>
    }
    return null
}

Fetch number of documents with filters

Parameters:

| No. | Variable | type | Required | Description | | --- | --------- | ------------------ | -------- | -------------------------------------------------------------- | | 1. | doctype | string | ✅ | Name of the doctype | | 2. | filters | Filter[] | - | optional parameter to filter the result | | 3. | cache | boolean | - | Whether to cache the value on the server - default: false | | 3. | debug | boolean | - | Whether to log debug messages on the server - default: false | | 3. | config | SWRConfiguration | - | SWR Configuration Options |

export const DocumentCount = () => {
  const { data, error, isValidating, mutate } = useFrappeGetDocCount(
    'User',
    /** Filters **/
    [['enabled', '=', true]],
    /** Cache the result on server **/
    false,
    /** Print debug logs on server **/
    false,
    {
      /** SWR Configuration Options - Optional **/
    }
  );

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return (
      <p>
        {data} enabled users
        <Button onClick={() => mutate()}>Reload</Button>
      </p>
    );
  }
  return null;
};

Some other simpler examples (click to expand):

export const DocumentCount = () => {
  const { data, error, isValidating } = useFrappeGetDocCount('User');

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return <p>{data} total users</p>;
  }
  return null;
};
export const DocumentCount = () => {
  const { data, error, isValidating } = useFrappeGetDocCount('User', [
    ['enabled', '=', true],
  ]);

  if (isValidating) {
    return <>Loading</>;
  }
  if (error) {
    return <>{JSON.stringify(error)}</>;
  }
  if (data) {
    return <p>{data} enabled users</p>;
  }
  return null;
};

Create a document

To create a new document, pass the name of the DocType and the fields to createDoc.

db.createDoc('My Custom DocType', {
  name: 'Test',
  test_field: 'This is a test field',
})
  .then((doc) => console.log(doc))
  .catch((error) => console.error(error));

Update a document

To update an existing document, pass the name of the DocType, name of the document and the fields to be updated to updateDoc.

db.updateDoc('My Custom DocType', 'Test', {
  test_field: 'This is an updated test field.',
})
  .then((doc) => console.log(doc))
  .catch((error) => console.error(error));

Delete a document

To create a new document, pass the name of the DocType and the name of the document to be deleted to deleteDoc.

db.deleteDoc('My Custom DocType', 'Test')
  .then((response) => console.log(response.message)) // Message will be "ok"
  .catch((error) => console.error(error));

API Calls

GET request

Make a GET request to your endpoint with parameters.

const searchParams = {
  doctype: 'Currency',
  txt: 'IN',
};
call
  .get('frappe.desk.search_link', searchParams)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

POST request

Make a POST request to your endpoint with parameters.

const updatedFields = {
  doctype: 'User',
  name: 'Administrator',
  fieldname: 'interest',
  value: 'Frappe Framework, ERPNext',
};
call
  .post('frappe.client.set_value', updatedFields)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

PUT request

Make a PUT request to your endpoint with parameters.

const updatedFields = {
  doctype: 'User',
  name: 'Administrator',
  fieldname: 'interest',
  value: 'Frappe Framework, ERPNext',
};
call
  .put('frappe.client.set_value', updatedFields)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

DELETE request

Make a DELETE request to your endpoint with parameters.

const documentToBeDeleted = {
  doctype: 'Tag',
  name: 'Random Tag',
};
call
  .put('frappe.client.delete', documentToBeDeleted)
  .then((result) => console.log(result))
  .catch((error) => console.error(error));

File Uploads

const myFile; //Your File object

const fileArgs = {
  /** If the file access is private then set to TRUE (optional) */
  "isPrivate": true,
  /** Folder the file exists in (optional) */
  "folder": "Home",
  /** File URL (optional) */
  "file_url": "",
  /** Doctype associated with the file (optional) */
  "doctype": "User",
  /** Docname associated with the file (mandatory if doctype is present) */
  "docname": "Administrator",
  /** Field to be linked in the Document **/
  "fieldname" : "image"
}

file.uploadFile(
            myFile,
            fileArgs,
            /** Progress Indicator callback function **/
            (completedBytes, totalBytes) => console.log(Math.round((c / t) * 100), " completed")
        )
        .then(() => console.log("File Upload complete"))
        .catch(e => console.error(e))

License

See LICENSE.