Single-Page Applications and kdb+: React
21 Jul 2020 | kdb+, q language
By Stephen Trainor, Kx.
A Single-Page Application (SPA) is a webpage that loads a single HTML page and dynamically updates that page as the user interacts through the browser – popular examples include NetFlix, Reddit and Dropbox. SPAs are beneficial industry-wide because they offer faster and more responsive browsing, as they don’t fetch separate HTML files for every page load, and perform more functionality client-side. The approach is useful when used in conjunction with Kx as it provides both the powerful data analysis of kdb+ and the rich visualization of Kx Dashboards. This approach may also make websites easier to maintain by kdb+ developers, as making the front-end code more dynamic could allow website changes to be made by changing q code instead of JS or HTML.
This blog post is the first of a series, which will demonstrate how different front-end technologies can be used with kdb+. The technology discussed in this post is called React (also known as ReactJS). React is a JavaScript (JS) library maintained by Facebook (React Native, which is similar to React but tailored to mobile-app development, is not examined here). A distinguishing feature of React is that it uses the JSX language, allowing the front-end code to be dynamically contained in JS files, rather than the traditional approach of maintaining JS and HTML files separately.
The SPA application discussed in this blog contains two tabs. The first is a calendar, which demonstrates cell editing and pagination. The second doesn’t contain any functionality and is just present to demonstrate URL routing for this technology.
Websockets
Websockets allow bi-directional, asynchronous data transfer between JS and q. These can be customized in q by editing the .z.ws event handler, and are covered in detail in a previous whitepaper.
When sending messages from JS to q, it is often cleaner to use h(func,param1,…paramN) instead of h”func[param1;…;paramN]”. For example, the .table.sort.q function in the template projects is passed an array of objects, which .j.k can parse into a table.
ws.qPromise(".table.sort",[[{"floats":1.595581, "longs":8,
"dates":"2002-09-19", "times":"13:01:02.753"}],2])
Copy
Rather than explicitly defining the table in q syntax on the front end:
ws.qPromise(“.table.sort[flip `floats`longs`dates`times!
enlist each (1.595581;8f;2002-09-19;13:01:02.753); 2]”
Copy
This method is also preferable for security, as noted in Q for Mortals:
“Allowing quoted q strings to be executed on a server makes the server susceptible to all manner of breaches. Good practice does not permit this on a production server. You can mitigate this by having your server process accept only requests whose first item is … the name of a function you have decided to expose.”
Javascript Promises
Promises are not exclusive to React, but are used in the discussed code, and are useful for neatly organising and chaining callback functions and they increase the maintainability of code.
d.q = function (id, func, obj, resolve, reject){
let ws = this.state.ws;
ws.postQ[id] = {resolve, reject};
this.setState({ws});
return ws.send(JSON.stringify({id,func,obj}));
};
d.qPromise = function(func, q, obj){
let id = this.state.id;
id+=1;
this.setState({id});
return new Promise(function(resolve,reject){
q(id, func, obj, resolve, reject);
})
};
Copy
The promise returned by d.qPromise, shown above, tells the front end to send an asynchronous command to q. The id is used to identify what functions need to be run when the response from kdb+ is received.
‘resolve’ and ‘reject’ are populated with the following .then or .catch functions, respectively:
- .then is used to perform a function which takes the result returned by the earlier function as an argument
- .catch is used to perform a function which takes the error message returned by the earlier function as an argument. This receives kdb+ errors such as ‘type.
- .finally is useful for disabling loading icons, or deleting variables after they’re no longer needed, but is not used in these template projects
this.qPromise(".cal.getHeight",this.q)
.then(this.postGetHeight)
.catch(function(e){alert(".cal.getHeight failed due to: "+e)})
.then(this.getCalendar);
Copy
REACT
Setting Up
- Download source code from Github
- Download npm
- Run npm update in the terminal from the reactProject directory, to install dependencies from package.json (bringing the project size to around 163MB)
- Run downloaded startR.bat and startQ.bat files – if you aren’t using C:\q\w64, startQ will require editing
- View from a browser: http://localhost:3000
JSX, State and Props
The philosophy of React is that JS and HTML are arbitrarily separated in other technologies, whereas React opts to use a syntax extension called JSX, which allows the rendering of HTML using JS code. Furthermore, JS files are separated into ‘components’, in which data flows from the top component to the bottom. Since this is a one-way data binding, an event handler needs to be defined to overwrite the variable if the user updates a form. This is the structure of the template project:
- index.js
- app.js
- tabs.js
- calendar.js
- table.js
- tabs.js
- app.js
Data is stored in App.js in an object called ‘state’, though state can be defined in lower components, if desired:
this.state={
index, monthMessage:"", height:0, id:0, sort:0, isNight:false, activeConnection:false
};
Copy
Functions and variables can then be passed to lower components (which by convention are the same names as their js files) through attributes called props:
There is a built-in function called this.setState, which can be used to update this.state. If we have a variable called this.state.index, running the below tells React to re-render any components which use this variable, whereas this.state.index=0 would not.
index = 0;
this.setState({index});
Copy
React Libraries
create-react-app was used to create the template project; which tracks file-caching, and automatically updates the web-page if CSS files are changed
react-devtools enables developers to view the written code and the state and props of each component, using Chrome dev tools
react-router enables predefined url paths to load corresponding JS files
react-contenteditable allows the use of the native contenteditable attribute, which isn’t compatible with React by default
Sample Project
React SPA – Consists of a calendar with editable cells, and a separate tab to demonstrate routing
Rendering a table
kdb+
In the calendar tab, a table with 8 rows is displayed on the front end, and refreshes as the user scrolls:
.cal.getCalendar:{[index]
t:update hiddenIndex:i from cal;
select ["j"$index,8] from t
};
Copy
React
createTable returns:
<React.Fragment>
<thead>
{this.createHead(t)}
</thead>
<tbody>
{this.createBody(t)}
</tbody>
</React.Fragment>
Copy
createHead returns a tr element, whose contents are dynamically generated by pushing th elements into an array called children:
At npm build, React returns warning messages if the ‘key’ attribute isn’t provided. The key attribute is an optional optimisation which helps React to rerender the minimum number of rows when a user-change occurs. Similarly, createBody returns an array of tr elements, each containing an array of td elements:
children.push(<th key={JSON.stringify(j)} >{(x[j])}</th>)
Copy
Editing a table
kdb+
When the user edits a cell in the calendar tab, .cal.editRow is called. It formats the text provided by the user, and overwrites that table cell in memory using a functional select
.cal.editRow:{[index; kolName; kolVal]
index:"j"$index;
kolName:`$kolName;
t:`cal;
kolType:type (value t)[kolName];
//Only include numbers in number fields
if[kolType in "h"$5+til 5; kolVal@:where kolVal in .Q.n,"-."];
//Cast to the appropriate datatype
kolVal:(neg kolType)$kolVal;
if[kolType=0h; kolVal:(enlist; kolVal)];
if[kolType=11h; kolVal:enlist kolVal];
//update kolName:kolVal from cal where i=index
![t; enlist(=;`i;index); 0b; (enlist kolName)!enlist kolVal];
}
Copy
React
Calendar.js contains
let html = `<td>${cells[j]}</td>`;
children.push(
<ContentEditable
key={i+","+j}
innerRef={React.createRef()}
html={html}
disabled={!editable}
value={cells[j]}
onChange={(e) => this.props.cellChange(e, i, cols[j])}
onBlur={this.props.editRow}
tagName='td'
className={cn}
/>
);
Copy
0nChange is used to trigger a function when the cell is edited:
d.cellChange = function(e, i, kol){
this.state.edit=[i,kol,e.target.value];
}
Copy
onBlur is used to trigger a function when the cell gains or loses focus.
d.editRow = function(e){
//i, kol, text
if(this.state.edit!==undefined){
let table = this.state.table;
let input = this.state.edit;
//Persist the text change on the front end, as well as the backend
table[input[0]][input[1]]=input[2];
this.setState({table});
input[0]+=this.state.index;
this.qPromise(".cal.editRow",this.q,input)
.then(this.postEditRow)
.catch(this.error)
};
};
Copy
In conclusion, React is a useful SPA library which can provide a fast and responsive browsing experience. This blog discussed its unique features, such as JSX, State and Props, and should serve as a suitable introduction to getting started with React and coupling it with kdb+.
The front and back-end code outlined above are available in GitHub on this link
Sponsored by Kx.