How to generate tables rows dynamically in Nodejs using ESCPOS API module - node.js

A few days ago I was struggling to get to print receipts using ESCPOS Java API but failed since I couldn't find a simple enough USB API to allow the program to print to my thermal printer using a USB interface. I later decided to implement this in Node and ESCPOS module.
I am connecting this application to MySQL database to be able to print receipts from recorded transactions. I am using tables to create a list of products and their respective prices but only by hard coding. My problem now is how to create these tables dynamically. Depending on which products were involved in the transaction, I want the script to query and print only those.
Here is receipt code!
const db = require('./database.js');
const escpos = require('escpos');
const uniqueRandom = require('unique-random');
const random = uniqueRandom(1000, 9999);
const device = new escpos.USB();
const options = { encoding: "GB18030" }
exports.printGeneralReceipt = function(status){
db.getTransactionDetails(function(res){
data = res;
});
const printer = new escpos.Printer(device, options);
console.log("Printer found!");
device.open(function(){
console.log("Receipt generating...");
printer
.font('b')
.align('ct')
.style('bu')
.size(1, 1)
.encode('utf8')
.text('\n*****START OF LEGAL RECEIPT*****'+
'\n\nCOMPUTICKET MALAWI\n'+
'SHOP 31A, GAME COMPLEX\n'+
'LILONGWE MALL\n\nwww.computicket.mw\n+265 (0) 99 974 7576\n')
.table(["BUYER NAME :", "CLIFFORD MWALE", ""])
.table(["RECEIPT # :", random(), ""])
.table(["DATE: ", "12/AUG/2019", ""])
.text("----------ITEM LIST----------\n")
// ITEM LIST STARTS HERE
.table(["MILK","$2"])
.table(["PEANUT BUTTER", "$6"])
// ITEM LIST ENDS HERE
.text("--------------------------------")
.table(["TOTAL PRICE", "$8.00", ""])
.text("Operator: Jon Doe\n-------------------------------\n")
.barcode('123456789012')
.text("\n\nTHANK YOU\n\n*****END OF LEGAL RECEIPT*****")
.beep(1,100)
.cut().close();
console.log("Receipt printed!");
});
}
And here is the function pulling the transaction details from the Database. I'll spare you the overhead of creating a connection.
exports.getTransactionDetails = function(trans_id){
var res = "";
conn.query("SELECT * FROM transactions JOIN products_in_transaction WHERE transactions.trans_id = products_in_transaction.trans_id "+
" transactions.trans_id = '"+trans_id+"'",
function (error, results, fields) {
for(var i = 0; i < results.length; i++){
// SOME OPERATION HERE
}
});
}

I will post some code I played with, and it worked;you need to install html-to-text
const escpos = require('escpos');
// Select the adapter based on your printer type
const device = new escpos.USB();
const printer = new escpos.Printer(device);
const cartItems = [
{category: 'test', price: 80, quantityToSell: 2, title: 'Hosting'},
{category: 'test1', price: 820, quantityToSell: 63, title: 'Mouse'},
{category: 'test00', price: 60, quantityToSell: 20, title: 'Sale'},
{category: 'dvhfgnfgjfjg', price: 20, quantityToSell: 8, title: 'Keyboards'},
{category: 'dvhfgnfgjfjg', price: 10, quantityToSell: 4, title: 'Keyss'},
{category: 'dvhfgnfgjfjg', price: 70, quantityToSell: 1, title: 'Test'},
{category: 'dvhfgnfgjfjg', price: 500, quantityToSell: 12, title: 'Whale oil'},
{category: 'dvhfgnfgjfjg', price: 560, quantityToSell: 22, title: 'Papers'},
]
// get total per line items
const totalPerItemList = (item) => {
let totalPerItem = 0
totalPerItem = item.quantityToSell * item.price
return totalPerItem
}
// get the total price
let total = 0;
for (let cartItem of cartItems) {
var unitSum = cartItem.quantityToSell * cartItem.price
total += unitSum
}
// Create our html template, could be an html file on it's own
const TestTable = `
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Testing Title for table</title>
</head>
<body>
<div class="invoice-box">
<table class="receipt-table" cellpadding="0" cellspacing="0" border="0">
<thead>
<tr class="heading">
<th>Item</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
${cartItems.map(item =>
`
<tr>
<td>${item.title}</td>
<td>${item.quantityToSell}</td>
<td>${item.price}</td>
<td>${totalPerItemList(item)}</td>
</tr>
`
)}
</tbody>
<tfoot>
<tr>
<td>
TOTAL:${total}
</td>
</tr>
</tfoot>
</table>
</div>
</body>
</html>
`
const htmlToText = require('html-to-text');
const text = htmlToText.fromString(TestTable, {
wordwrap: false,
tables: ['.receipt-box', '.receipt-table']
});
device.open(function(err){
printer
.font('a')
.align('ct')
.style('bu')
.size(1, 1)
.text('Printing Tables Dynamically with epos')
.text('||||||||||||||||||||||||||')
.text(text)
.text('||||||||||||||||||||||||')
.text('========================')
.cut()
.close()
});

the fastest way to do this ive found is to do what ive done in my own project as in
'use strict'
import { app, protocol, BrowserWindow,ipcMain } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const escpos = require('escpos');
escpos.USB = require('escpos-usb');
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
}
})
win.menuBarVisible = false;
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
function print(load){
var _message = '';
device.open(function(){
_message = 'done'
printer
.font('a')
.align('ct')
.style('bu')
.size(0.05, 0.05)
.text('Road House Magodo')
.text('Magodo Shopping Arcade Ayodele \r\n Fanoki Magodo Phase I')
.table(['item','qty','total'])
load.load.forEach((element)=>{
return printer.table(element)
})
printer.text(`Total: ${load.total}`)
if(load.change!='card'){
printer.text(`Change: ${load.change}`)
}else{
printer.text('Method: Card')
}
printer.newLine()
printer.newLine()
printer.cut()
printer
.font('a')
.align('ct')
.style('bu')
.size(0.05, 0.05)
.text('Road House Magodo')
.text('Magodo Shopping Arcade Ayodele \r\n Fanoki Magodo Phase I')
.table(['item','qty','total'])
load.load.forEach((element)=>{
return printer.table(element)
})
printer.text(`Total: ${load.total}`)
if(load.change!='card'){
printer.text(`Change: ${load.change}`)
}else{
printer.text('Method: Card')
}
printer.newLine()
printer.newLine()
printer.cut()
printer
.font('a')
.align('ct')
.style('bu')
.size(0.05, 0.05)
.text('Road House Magodo')
.text('Magodo Shopping Arcade Ayodele \r\n Fanoki Magodo Phase I')
.table(['item','qty','total'])
load.load.forEach((element)=>{
return printer.table(element)
})
printer.text(`Total: ${load.total}`)
if(load.change!='card'){
printer.text(`Change: ${load.change}`)
}else{
printer.text('Method: Card')
}
printer.newLine()
printer.cut()
printer.close()
},error=>{
if(error){
console.log(error)
_message = error+'error'
return
}
})
return _message
}
const device = new escpos.USB();
const printer = new escpos.Printer(device);
ipcMain.on('print', (_,load) => {
let _fish =()=>{
return print(JSON.parse(load))
}
_.reply(_fish());
})
basically passing your array or object and looping through it.

Related

Discord.js | How to create a limit of .addFields objects, creating a list with .addFields with different value of an Array?

I created a script to do tests, I want to make a listing with 4 objects inside the .addfields, if you have more than 4, create an embed throwing other data in the objects inside the .addfields in it.
const Command = require("../../structures/Command");
const { EmbedBuilder, ModalBuilder, TextInputComponent, ButtonBuilder, ButtonStyle, ApplicationCommandType, ActionRowBuilder, SelectMenuOptionBuilder, SelectMenuBuilder} = require('discord.js');
module.exports = class extends Command {
constructor(client) {
super(client, {
name: 'shop',
description: 'shop',
type: ApplicationCommandType.ChatInput
});
};
run = async (interaction) => {
let listItems = ['Whinds', 'Freeway', 'Teste', 'Maria'];
const embed = [];
let i = 0;
listItems.forEach(e => {
if (!embed[Math.floor(i / 60)]) {
embed.push(new EmbedBuilder()
.setDescription(`testando`)
.addFields(
listItems.map(item => (
{
name: `${item[i]} [R$ ${item[i]}]`, value: `${item[i]}`}
))
)
);
}
i++;
})
console.log(embed)
embed.forEach(e => {
interaction.reply({embeds: [e] });
})
}
}
I would like support, how can I make this listing system with a maximum of 4 objects in the embed addfields.

Channel not being connected when trying to enable one on one chat in Stream (JS)

I have an app I'm building where one user (called admin) has a one on one channel with most users. My understanding is that only one channel will exist when I create a one on one chat, regardless of who (admin or user) creates it first. My issue is that when I add a user and a new one on one chat should be established, other channels are being duplicated in my channel list as well. Either that, or sometimes the channel just doesn't connect at all, and the user is left without a chat. The code/pseudo-code I am using is below, does anyone know how to create multiple one on one chats using stream, or why certain channels might be connecting twice?
P.S. I'm editing the code to simplify it to only relevant parts here on stack overflow, so please ignore any simple syntax errors below. Thanks!
setupUserChats = async (chatClient) => {
let { currentUser, participants } = this.props;
if (the current user is an admin) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (if the participant is a user) {
let conversation = await chatClient.channel('messaging', {
name: participant.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
if (currentUser is a user) {
for (let i = 0; i < participants.length; i++) {
let participant = participants[i];
if (participant is an admin) {
let conversation = await chatClient.channel('messaging', {
name: currentUser.name,
members: [`${currentUser.participant_id}`, `${participant.participant_id}`]
});
await conversation.watch();
}
}
}
this.setState({ chatClient: chatClient });
}
I call another function (outlined below) to set up the basic chat, and then call the above code inside of this next function:
setupChat = async () => {
let { currentUser, participants } = this.props;
let chatClient = await new StreamChat(process.env.REACT_APP_STREAM_API_KEY);
let serverResult = await getStreamToken(currentUser.participant_id);
let userToken = serverResult.token;
await chatClient.setUser(
{
id: currentUser.participant_id,
name: currentUser.name,
},
userToken,
);
const moderatorToAdmin = await chatClient.channel('messaging', `conference-ID`, {
name: "moderator-chat"
});
await moderatorToAdmin.watch();
this.setupUserChats(chatClient);
moderatorToAdmin.addMembers([`${currentUser.participant_id}`]);
this.setState({ chatClient: chatClient });
};
And then its all rendered here:
render() {
const { currentUser } = this.props;
const filters = { members: { $in: [currentUser.participant_id] } }
return (
<div>
<Chat client={this.state.chatClient} theme={'messaging light'} >
<div className="title">Chat</div>
<ChannelList
filters={filters}
Preview={MyChannelPreview}
/>
<Channel Message={MyMessageComponent} >
<Window>
<MessageList />
<MessageInput />
</Window>
<Thread />
</Channel>
</Chat>
</div>
);
}
class MyChannelPreview extends React.Component {
render() {
const { setActiveChannel, channel } = this.props;
return (
<div className={`channel_preview` + currentChannelClass}>
<a href="#" onClick={(e) => setActiveChannel(channel, e)}>
{channel._data.name || channel.data.name}
</a>
</div>
);
}
}

how can I pass data like the name of my user and put it in the post they created

so I am making an application for events and for some reason when a user creates an event the even info shows but the user info like their name and photo doesn't show up please help I've been having this problem for almost a week now.
THIS IS THE componentDidMount function
async componentDidMount() {
const { data } = await getCategories();
const categories = [{ _id: "", name: "All Categories" }, ...data];
const { data: events } = await getEvents();
this.setState({ events, categories });
console.log(events);
}
THIS IS THE STATE
class Events extends Component {
state = {
events: [],
user: getUser(),
users: getUsers(),
showDetails: false,
shownEventID: 0,
showUserProfile: false,
shownUserID: 0,
searchQuery: ""
};
THIS IS THE EVENTS FILE WHERE THE USER'S NAME AND PHOTO SHOULD BE DISPLAYED
<Link>
<img
className="profilePic mr-2"
src={"/images/" + event.hostPicture}
alt=""
onClick={() => this.handleShowUserProfile(event.userId)}
/>
</Link>
<Link style={{ textDecoration: "none", color: "black" }}>
<h4
onClick={() => this.handleShowUserProfile(event.userId)}
className="host-name"
>
{getUser(event.userId).name}
</h4>
</Link>
This is the userService file where the getUser function is
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/users";
export function register(user) {
return http.post(apiEndPoint, {
email: user.email,
password: user.password,
name: user.name
});
}
export function getUsers() {
return http.get(apiEndPoint);
}
export async function getUser(userId) {
const result = await http.get(apiEndPoint + "/" + userId);
return result.data;
}
This is the eventService file where the event is
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/events";
export function getEvents() {
return http.get(apiEndPoint);
}
export function getEvent(eventId) {
return http.get(apiEndPoint + "/" + eventId);
}
export function saveEvent(event) {
if(event._id){
const body = {...event}
delete body._id
return http.put(apiEndPoint + '/' + event._id, body)
}
return http.post(apiEndPoint, event);
}
export function deleteEvent(eventId) {
return http.delete(apiEndPoint + "/" + eventId);
}
First, you have some mistakes to use the class in <div> elements.
please use className instead class.
And then second I am not sure what it is.
class Events extends Component {
state = {
... ...
user: getUser(),
... ...
};
As you seen getUser() function requires one parameter userId.
But you did not send this.
So you met internal server error to do it.
Since I did not investigate all projects, I could not provide perfectly solution.
However, it is main reason, I think.
Please check it.

How can I use Esri Arcgis Map in ReactJs Project?

I'm trying to use Esri map. To include map in my project, here is what I found:
require([
"esri/map",
"esri/dijit/Search",
"esri/dijit/LocateButton",
"esri/geometry/Point",
"esri/symbols/SimpleFillSymbol",
"esri/symbols/SimpleMarkerSymbol",
"esri/symbols/SimpleLineSymbol",
But there isn't any esri folder or npm package. Therefore, I'm confused here. How esri is imported in project?
Use esri-loader to load the required esri modules. This is a component rendering basemap.
import React, { Component } from 'react';
import { loadModules } from 'esri-loader';
const options = {
url: 'https://js.arcgis.com/4.6/'
};
const styles = {
container: {
height: '100vh',
width: '100vw'
},
mapDiv: {
padding: 0,
margin: 0,
height: '100%',
width: '100%'
},
}
class BaseMap extends Component {
constructor(props) {
super(props);
this.state = {
status: 'loading'
}
}
componentDidMount() {
loadModules(['esri/Map', 'esri/views/MapView'], options)
.then(([Map, MapView]) => {
const map = new Map({ basemap: "streets" });
const view = new MapView({
container: "viewDiv",
map,
zoom: 15,
center: [78.4867, 17.3850]
});
view.then(() => {
this.setState({
map,
view,
status: 'loaded'
});
});
})
}
renderMap() {
if(this.state.status === 'loading') {
return <div>loading</div>;
}
}
render() {
return(
<div style={styles.container}>
<div id='viewDiv' style={ styles.mapDiv } >
{this.renderMap()}
</div>
</div>
)
}
}
export default BaseMap;
This renders a base map but this is not responsive. If I remove the div around the view div or if I give the height and width of the outer div (surrounding viewDiv) as relative ({ height: '100%', width: '100%'}), the map does not render. No idea why. Any suggestions to make it responsive would be appreciated.
An alternative method to the above is the one demonstrated in esri-react-router-example. That application uses a library called esri-loader to lazy load the ArcGIS API only in components/routes where it is needed. Example:
First, install the esri-loader libary:
npm install esri-loader --save
Then import the esri-loader functions in any react module:
import * as esriLoader from 'esri-loader'
Then lazy load the ArcGIS API:
componentDidMount () {
if (!esriLoader.isLoaded()) {
// lazy load the arcgis api
const options = {
// use a specific version instead of latest 4.x
url: '//js.arcgis.com/3.18compact/'
}
esriLoader.bootstrap((err) => {
if (err) {
console.error(err)
}
// now that the arcgis api has loaded, we can create the map
this._createMap()
}, options)
} else {
// arcgis api is already loaded, just create the map
this._createMap()
}
},
Then load and the ArcGIS API's (Dojo) modules that are needed to create a map:
_createMap () {
// get item id from route params or use default
const itemId = this.props.params.itemId || '8e42e164d4174da09f61fe0d3f206641'
// require the map class
esriLoader.dojoRequire(['esri/arcgis/utils'], (arcgisUtils) => {
// create a map at a DOM node in this component
arcgisUtils.createMap(itemId, this.refs.map)
.then((response) => {
// hide the loading indicator
// and show the map title
// NOTE: this will trigger a rerender
this.setState({
mapLoaded: true,
item: response.itemInfo.item
})
})
})
}
The benefit of using esri-loader over the approach shown above is that you don't have to use the Dojo loader and toolchain to load and build your entire application. You can use the React toolchain of your choice (webpack, etc).
This blog post explains how this approach works and compares it to other (similar) approaches used in applications like esri-redux.
You don't need to import esri api like you do for ReactJS. As the react file will finally compile into a js file you need to write the esri parts as it is and mix the ReactJS part for handling the dom node, which is the main purpose of ReactJS.
A sample from the links below is here
define([
'react',
'esri/toolbars/draw',
'esri/geometry/geometryEngine',
'dojo/topic',
'dojo/on',
'helpers/NumFormatter'
], function(
React,
Draw, geomEngine,
topic, on,
format
) {
var fixed = format(3);
var DrawToolWidget = React.createClass({
getInitialState: function() {
return {
startPoint: null,
btnText: 'Draw Line',
distance: 0,
x: 0,
y: 0
};
},
componentDidMount: function() {
this.draw = new Draw(this.props.map);
this.handler = this.draw.on('draw-end', this.onDrawEnd);
this.subscriber = topic.subscribe(
'map-mouse-move', this.mapCoordsUpdate
);
},
componentWillUnMount: function() {
this.handler.remove();
this.subscriber.remove();
},
onDrawEnd: function(e) {
this.draw.deactivate();
this.setState({
startPoint: null,
btnText: 'Draw Line'
});
},
mapCoordsUpdate: function(data) {
this.setState(data);
// not sure I like this conditional check
if (this.state.startPoint) {
this.updateDistance(data);
}
},
updateDistance: function(endPoint) {
var distance = geomEngine.distance(this.state.startPoint, endPoint);
this.setState({ distance: distance });
},
drawLine: function() {
this.setState({ btnText: 'Drawing...' });
this.draw.activate(Draw.POLYLINE);
on.once(this.props.map, 'click', function(e) {
this.setState({ startPoint: e.mapPoint });
// soo hacky, but Draw.LINE interaction is odd to use
on.once(this.props.map, 'click', function() {
this.onDrawEnd();
}.bind(this));
}.bind(this))
},
render: function() {
return (
<div className='well'>
<button className='btn btn-primary' onClick={this.drawLine}>
{this.state.btnText}
</button>
<hr />
<p>
<label>Distance: {fixed(this.state.distance)}</label>
</p>
</div>
);
}
});
return DrawToolWidget;
});
Below are the links where you can find information in detail.
http://odoe.net/blog/esrijs-reactjs/
https://geonet.esri.com/people/odoe/blog/2015/04/01/esrijs-with-reactjs-updated

How to render the React component with dynamic data realtime from socket.io high efficiency

My front-end page is made by React + Flux, which sends the script data to back-end nodejs server.
The script data is an Array which contains the linux shell arguments (more than 100000). When to back-end received, it will execute the linux shell command.
Just an example:
cat ~/testfile1
cat ~/testfile2
.
.
.
(100000 times ...etc)
When the backend finished one of the linux shell commands, I can save the readed content to result data. Therefore, socket.io will emit the result data to the front-end.
I want to get the result data from my webpage in real time, so I have done some stuff in my project below.
My React component code:
import React from 'react';
import AppActions from '../../../actions/app-actions';
import SocketStore from '../../../stores/socket-store';
import ResultStore from '../../../stores/result-store';
function getSocket () {
return SocketStore.getSocket();
}
function getResult () {
return ResultStore.getResultItem();
}
class ListResultItem extends React.Component {
constructor () {
super();
}
render () {
return <li>
{this.props.result.get('name')} {this.props.result.get('txt')}
</li>;
}
}
class ShowResult extends React.Component {
constructor () {
super();
this.state = {
socket: getSocket(),
result: getResult()
};
}
componentWillMount () {
ResultStore.addChangeListener(this._onChange.bind(this));
}
_onChange () {
this.setState({
result: getResult()
});
}
render () {
return <div>
<ol>
{this.state.result.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})}
</ol>
</div>;
}
componentDidMount () {
this.state.socket.on('result', function (data) {
AppActions.addResult(data);
});
}
}
My Flux store (ResultStore) code:
import AppConstants from '../constants/app-constants.js';
import { dispatch, register } from '../dispatchers/app-dispatcher.js';
import { EventEmitter } from 'events';
import Immutable from 'immutable';
const CHANGE_EVENT = 'changeResult';
let _resultItem = Immutable.List();
const _addResult = (result) => {
let immObj = Immutable.fromJS(result);
_resultItem = _resultItem.push(immObj);
}
const _clearResult = () => {
_resultItem = _resultItem.clear();
}
const ResultStore = Object.assign(EventEmitter.prototype, {
emitChange (){
this.emit( CHANGE_EVENT );
},
addChangeListener (callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener (callback) {
this.removeListener(CHANGE_EVENT, callback);
},
getResultItem () {
return _resultItem;
},
dispatcherIndex: register(function (action) {
switch (action.actionType) {
case AppConstants.ADD_RESULT:
_addResult(action.result);
break;
case AppConstants.CLEAR_RESULT:
_clearResult();
break;
}
ResultStore.emitChange();
})
});
However, the page will become very slow after rendering more than 1000 datas. How to enhance the performance for rendering? I need to execute the linux script persistently more than 3 days. Any solutions? Thanks~
Is there any need to render all the data on screen? If not then there are a few ways to deal with cutting down the amount of visible data.
Filter / Search
You can provide a search/filter component that complements the list and creates a predicate function that can be used to determine whether each item should or should not be rendered.
<PredicateList>
<Search />
<Filter />
{this.state.result
.filter(predicate)
.map(function(item, index) {
return <ListResultItem key={index} result={item} />;
})
}
</PredicateList>
Lazy Load
Load the items only when they are asked for. You can work out whether item is needed by keeping track of whether it would be onscreen, or whether the mouse was over it.
var Lazy = React.createClass({
getInitialState: function() {
return { loaded: false };
},
load: function() {
this.setState({ loaded: true });
},
render: function() {
var loaded = this.state.loaded,
component = this.props.children,
lazyContainer = <div onMouseEnter={this.load} />;
return loaded ?
component
lazyContainer;
}
});
Then simply wrap your data items inside these Lazy wrappers to have them render when they are requested.
<Lazy>
<ListResultItem key={index} result={item} />
</Lazy>
This ensures that only data needed by the user is seen. You could also improve the load trigger to work for more complex scenarios, such as when the component has been onscreen for more then 2 seconds.
Pagination
Finally, the last and most tried and tested approach is pagination. Choose a limit for a number of data items that can be shown in one go, then allow users to navigate through the data set in chunks.
var Paginate = React.createClass({
getDefaultProps: function() {
return { items: [], perPage: 100 };
},
getInitialState: function() {
return { page: 0 };
},
next: function() {
this.setState({ page: this.state.page + 1});
},
prev: function() {
this.setState({ page: this.state.page - 1});
},
render: function() {
var perPage = this.props.perPage,
currentPage = this.state.page,
itemCount = this.props.items.length;
var start = currentPage * perPage,
end = Math.min(itemCount, start + perPage);
var selectedItems = this.props.items.slice(start, end);
return (
<div className='pagination'>
{selectedItems.map(function(item, index) {
<ListResultItem key={index} result={item} />
})}
<a onClick={this.prev}>Previous {{this.state.perPage}} items</a>
<a onClick={this.next}>Next {{this.state.perPage}} items</a>
</div>
);
}
});
These are just very rough examples of implementations for managing the rendering of large amounts of data in efficient ways, but hopefully they will make enough sense for you to implement your own solution.

Resources