Skip to content

Repository files navigation

React

Plugin to integrate React components in your Saltcorn application.

Example to get started

In this example, you will create two bundle.js files: A global main-bundle containing React and your own components, and a second view-bundle with shared access to the main file.

Main bundle

  1. Create a directory on your server file system or use the Saltcorn files manager, call it getting-started-lib.
  2. In getting-started-lib, create a package.json and an index.js file:
{
"name": "getting-started-lib",
"version": "0.0.1",
"description": "Basic components lib",
"main": "index.js",
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
importReactfrom"react";functionPersonsList({ rows }){return(<div>{rows&&rows.length
? rows.map((row)=>{returnObject.entries(row).map(([key,value])=>(<divkey={key}><h3>{key}: {value}</h3></div>));})
: "No data available"}</div>);}exportconstcomponents={
PersonsList,};
  1. Install the react plugin and open the plugin configuration.
  2. Depending on step 1, set Code source to local or Saltcorn folder and select or enter the directory with the package.json and index.js file.
  3. Set the Build mode to development and click build or Finish.
    When everything went well, you now have a global main bundle with React and your getting-started-lib.

View bundle

  1. Create a view with the React Pattern, select any table and call the view gettingStartedView
    (Please don't use white spaces or other special characters).
  2. In the view configuration, enter this user code:
importReactfrom"react";const{ PersonsList }=reactUserLib.components;exportdefaultfunctionApp({ rows }){return<PersonsListrows={rows}/>;}

and click Build or Finish.

  1. Open the view, and you should see a simple listing of your rows.

The component also has access to state, query, rows and the current user:

exportdefaultfunctionApp({ tableName, viewName, state, query, rows, user, stateHash, totalCount })
  • stateHash — short hash for this view's state context, used to construct pagination/sort URL params
  • totalCount — total number of matching rows (only set when a pagesize is active), useful for "page X of Y"

For tableless react-views, only the following properties are available:

exportdefaultfunctionApp({ viewName, query, state, user, stateHash })

Define everything in the view

The above example has PersonsList in the main bundle, and the view only uses the component. This makes the component reusable, and you can edit it with your favorite IDE. But you don't have to split it, you could also write everything in the views user-code, and if you want, set Code source to Not set in the plugin configuration.

The view code would look like this:

importReactfrom"react";exportdefaultfunctionApp({ rows }){return(<div>{rows&&rows.length
? rows.map((row)=>{returnObject.entries(row).map(([key,value])=>(<divkey={key}><h3>{key}: {value}</h3></div>));})
: "No data available"}</div>);}

Plugin configuration

The plugin configuration has the following options:

  • Code source: Describes the source type where you get your React code from. The options are:
    • Saltcorn folder: The React code is in a folder in the Saltcorn directory. The folder is selected in the input below.
    • Local: To get the code from a folder on your local server file system (Path to code input).
    • Not set: No code source is set. You define the code completely in the view configuration.
  • Build mode: Build your bundle.js for development or production. Use development for debugging and production for minified deployment code. The size of the main bundle changes significantly, please avoid development in production.

CSS files & images

To integrate CSS files or images, put them into the project referenced by Code source and use the import Syntax. For example:

import"../resources/persons_list.css";importbannerImagefrom"../resources/banner.png";exportdefaultfunctionPersonsList({ ... }){
...
}

Act as a Filter

A Saltcorn Filter modifies the query of the browser window, for example, with the value from an input. To mimic this, you can create a component with an input field and call set_state_field on change (or onblur if you don't want to change it on every keystroke):

importReactfrom"react";exportdefaultfunctionApp({ viewName, query }){return(<div><labelhtmlFor="nameFilter">Filter by Name:</label><inputtype="text"id="nameFilter"value={query.name||""}onChange={(e)=>{set_state_field("name",e.target.value,e);}}/></div>);}

You also could set the initial value, with a value from query (if there's a matching value).

react-lib

The system gives you access to react-lib. This is a module with hooks, functions and components to interact with the Saltcorn system. To use it, add it as peerDependency in your components-lib.
The following examples are basic, more complete code can be found here.

Read rows

To fetch multiple rows, you can use this structure:

import{useFetchRows}from"@saltcorn/react-lib/hooks";exportdefaultfunctionApp({ tableName, viewName, state, query }){const{ rows, isLoading, error }=useFetchRows(tableName,query);return<div>{rows&&rows.map((row)=>(
...
))}{error&&<p>Error: {error}</p>}
</div>}

For only one row:

importReactfrom"react";import{useFetchOneRow}from"@saltcorn/react-lib/hooks";exportdefaultfunctionApp({ tableName, viewName, state, query }){const{ row, isLoading, error }=useFetchOneRow(tableName,query);return<div>...</div>;}

If you don't need hooks:

import{fetchRows,fetchOneRow}from"@saltcorn/react-lib/api";

Count rows

importReactfrom"react";import{useCountRows}from"@saltcorn/react-lib/hooks";exportdefaultfunctionApp({ viewName, query }){const{ count, isCounting, error }=useCountRows("users");return(<div><h3>
Row count for users:{" "}{isCounting ? "Count..." : error ? "Error fetching data" : count}</h3></div>);}

Or without hooks:

import{countRows}from"@saltcorn/react-lib/api";

Modify rows

To insert, update or delete rows, take a look at this basic examples:

Insert row

importReactfrom"react";import{insertRow}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({ tableName, viewName, state, query }){return(<buttononClick={()=>insertRow(tableName,{name: "John Doe",age: 30})}>
Insert row
</button>);}

Update row

importReactfrom"react";import{updateRow}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({ tableName, viewName, state, query }){return(<buttononClick={()=>updateRow(tableName,1,{name: "Jane Doe",age: 31})}>
Update row
</button>);}

Delete row

importReactfrom"react";import{deleteRow}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({ tableName, viewName, state, query }){return<buttononClick={()=>deleteRow(tableName,1)}>Delete row</button>;}

Run Saltcorn Actions

Use the runAction function to trigger Saltcorn actions:

importReactfrom"react";import{runAction}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({}){return<buttononClick={()=>runAction("my_action")}>Run action</button>;}

You'll need a trigger named my_action with the When condition Api call. runAction is asynchronous, and data returned from the run will be set it in the response. The following example uses run_sql_query from the sql plugin to query all existing users:

importReact,{useState}from"react";import{runAction}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({ viewName, query }){const[users,setUsers]=useState([]);constfetchUsers=async()=>{try{constresponse=awaitrunAction("select_all_users");setUsers(response);}catch(error){console.error("Error fetching users:",error);}};return(<divclassName="container mt-5"><buttonclassName="btn btn-primary mb-3"onClick={fetchUsers}>
Fetch Users
</button><tableclassName="table table-bordered table-striped"><thead><tr><th>ID</th><th>Email</th></tr></thead><tbody>{users.length>0 ? (users.map((user,index)=>(<trkey={index}><td>{user.id}</td><td>{user.email}</td></tr>))) : (<tr><tdcolSpan="3"className="text-center">
No users found.
</td></tr>)}</tbody></table></div>);}

For this, you need a trigger named select_all_users with the Api call condition, and the action should be run_sql_query. In select_all_users you can use

SELECT id, name FROM users;

Actions with row

For actions that need a row, call runAction like this:

awaitrunAction(actionName,row);

For example, to count all users that have a specific email ending, you could write this run_sql_query trigger:

SELECTCOUNT(*) AS num_endings
FROM users
WHERE email LIKE'%'|| $1;

Name it count_email_endings and set 'Row parameters' to email_ending.

With this, you could write a view that takes the ending from an input field and calls the action:

importReact,{useState}from"react";import{runAction}from"@saltcorn/react-lib/api";exportdefaultfunctionApp({ viewName, query }){const[userCount,setUserCount]=useState(null);const[emailEnding,setEmailEnding]=useState("");constcountUsers=async()=>{try{constresponse=awaitrunAction("count_email_endings",{email_ending: emailEnding,});setUserCount(response[0]?.num_endings);}catch(error){console.error("Error fetching users:",error);}};return(<divclassName="container mt-5"><divclassName="mb-3"><inputtype="text"className="form-control"placeholder="Enter email ending (e.g., gmail.com)"value={emailEnding}onChange={(e)=>setEmailEnding(e.target.value)}/></div><buttonclassName="btn btn-primary mb-3"onClick={countUsers}>
Fetch Users
</button>{userCount!==null&&(<divclassName="alert alert-info">Total Users: {userCount}</div>)}</div>);}

Components

ScView

The ScView component allows using normal Saltcorn views in your React component. The following example shows how to use the list_persons list-view:

importReactfrom"react";import{ScView}from"@saltcorn/react-lib/components";exportdefaultfunctionApp({ viewName, query }){return(<div><ScViewname="list_persons"query={query}/></div>);}

Controlling embedded multi-row views via URL state

A React view can embed and control other Saltcorn views that display multiple rows by reading and writing URL state. When URL state changes via pjax, Saltcorn re-renders the affected views and passes the updated state prop to your React component.

Note: Currently only List and Feed view templates are supported — they expose the pagination metadata that useManyView depends on.

Embedded views use URL params prefixed with a short hash unique to each view instance:

ParamEffect
_${hash}_pageCurrent page (1-based)
_${hash}_pagesizeRows per page
_${hash}_sortbyField name to sort by
_${hash}_sortdesc"on" for descending order

Use set_state_field(key, value) to update a single param, or set_state_fields({...}) to update several at once so they land in a single navigation step.

Full example

A tableless React controller view that embeds "persons_feed" (a Feed view — a List view works identically) and controls its pagination and sorting. It extracts the server-computed hash from the embedded view on first load, then keeps it in sync with URL state on every pjax navigation.

importReactfrom"react";import{useManyView}from"@saltcorn/react-lib/hooks";import{ScView}from"@saltcorn/react-lib/components";exportdefaultfunctionApp({ state }){const{ hash, page, hasNext, ready, viewProps }=useManyView("persons_feed",state);if(!ready)returnnull;constsortBy=state[`_${hash}_sortby`]||"";constsortDesc=!!state[`_${hash}_sortdesc`];constsetPage=(n)=>set_state_field(`_${hash}_page`,n);constsetSort=(field)=>{constnewDesc=sortBy===field ? (sortDesc ? "" : "on") : "";set_state_fields({[`_${hash}_sortby`]: field,[`_${hash}_sortdesc`]: newDesc});};return(<div><divclassName="d-flex flex-wrap align-items-center gap-2 p-2 bg-light rounded"><spanclassName="text-muted small">Sort:</span>{["first_name","last_name"].map((f)=>(<buttonkey={f}className={`btn btn-sm ${sortBy===f ? "btn-primary" : "btn-outline-secondary"}`}onClick={()=>setSort(f)}>{f}{sortBy===f ? (sortDesc ? " ▼" : " ▲") : ""}</button>))}<divclassName="vr"/><buttonclassName="btn btn-sm btn-outline-secondary"disabled={page<=1}onClick={()=>setPage(page-1)}>‹ Prev</button><spanclassName="small">Page {page}</span><buttonclassName="btn btn-sm btn-outline-secondary"disabled={!hasNext}onClick={()=>setPage(page+1)}>Next ›</button></div>{/* Hide the feed's built-in paginator — we provide our own above */}<style>{`.sc-feed-wrapper ul.pagination { display: none !important; }`}</style><ScView{...viewProps}className="sc-feed-wrapper"/></div>);}

Copilot

The Saltcorn copilot can generate react-views. Only views where all the code is stored within the view are possible, an action to change the main bundle does not exist yet. When the chat only gives you the code without a button to apply it, try to be more explicit (for example, it could be that the model still needs to know the min_role).

Setup

To make the Generate React View tool available in the Saltcorn Copilot:

  1. Make sure Agents is installed.
  2. Go to Actions and create a new Trigger with the Agent action.
  3. In the configuration, add Generate React View to the skill list.
  4. Open the Saltcorn Copilot trigger configuration, click the add button and select your Generate React View trigger.

About

integrate React components in your Saltcorn application

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages