This is a sample page to show how subpages can connect to other pages. Clear out this sample info to flesh it out with your team’s resources, or turn it into something new!
is a superset of JavaScript that provides static typing to the otherwise loosely-typed JavaScript. We use it because it provides the benefits of easy integration with the Node and React ecosystem while providing typing that ensures our code is well-structured and documented.
Coding guidelines
The TypeScript team publishes coding guidelines which are accessible
: great integration with TypeScript, especially since it's developed by the same company (Microsoft) as TS itself. If you do use it, be sure to install the workspace-recommended extensions. You may also want to enable auto-formatting on save
: Doesn’t have much out of the box, but you can get full TypeScript integration, etc with configuration and plugins. The terminal nerds at #dev-linux can help you 😉
Line Width
We generally keep line width to a maximum of 120 characters.
Indentation
We use two-space indentation per level.
Declarations
Local variables should be declared as consts if they are not reassigned. Use one declaration per line, with constants first, followed by initialized lets and finally uninitialized lets.
Global variables & constants should be capitalized.
export const LookupTable = {};
Arrow Functions
In general, prefer arrow functions for inline functions, unless you need to maintain context (this, arguments, etc.) of the caller. However, do not use arrow functions as a replacement for class level functions. Example to follow:
this._foo = 1;
setTimeout(() => {
alert(this._foo + 2); // = 3
}, 1000);
Note that using these kinds of bound functions can result in the context being lost when you need it. For instance, passing a callback that makes use of this to some other component would cause it to not work properly by default; you'll have to bind the function first before passing it like so -
grid.events.on('rowDeleted', this._dispose); // Fails as `this` used inside _dispose() may be bound to something else.
grid.events.on('rowDeleted', rowId => this._dispose(rowId)); // `this` context will work correctly here.
Note too that arrow functions are always recreated on every React rerender. This may lead to some unintended consequences. For instance, using arrow functions for setting React refs sometimes lead to race conditions where a component is mounted but its ref is null. Whenever possible, use bound methods on the class. So instead of:
Required parameters to methods should be specified directly, and methods should in general not have more than 4-5 parameters. Beyond that, consider refactoring to group related arguments into strongly-typed data objects. When a function uses default parameters, especially more than one, consider placing them into an optional object following required parameters, e.g.:
Import statements should be together in one block and consumers of those statements should be in a separate block. We have a Lint rule that will auto fix up imports (easiest to use if you have format-on-save enabled).
import {BaseComponent} from '@kr-browser/react-components/base_component';
import React from 'react';
import {SomeConstant} from '../../constants';
import {TooltipView} from '@kr-browser/react-components/tooltip_view';
import {Value} * '@kr-common/constants';
import classNames from 'classnames';
import * as domHelpers from '../../helpers/dom';
const {DOM: dom} = React;
Exporting
Most TypeScript files we work with are classes or constants. These should be exported as needed with the export keyword. index.ts files within each package should then re-export public objects/classes for use across other packages.
, but we’ve been moving away from it given ES6 and related functionality typically performs better. We usually prefer native implementations over lodash unless there’s a specific performance reason to do otherwise.
Property and matching shorthand notations
Lodash functions make use of several shorthand notations for working with data structures:
As with any library, keep in mind performance and readability. There are some functions in Lodash, such as _.rearg, that are usually better left alone. That is, sometimes a for of loop might be clearer than a complex sequence of seldom-used Lodash functions. Furthermore, avoid using something like _.size(arr) or _.first(arr) when arr.length or arr[0] would suffice: Lodash performs many runtime checks, which can be slow in hot code paths.
of how we use promises. AccountsManager#createUser() starts by verifying whether someone is allowed to create an account. If so, it asks the accounts storage Postgres implementation to create a new user in the users table, and proceeds to save the Google authentication token in a different table. If there is a failure along the way, the catch() block logs a warning and throws an unauthorized error.
Take care with returning values within try/catch!
Consider the following function:
async function processResult(data) {
try {
return someOtherAsyncFunction(data);
} catch (err) {
if (shouldHandleError(err)) {
return;
}
throw err;
}
}
It may be expected that errors thrown (or rather promise rejections from) someOtherAsyncFunction would flow into the catch block. This is not the case. Instead, the promise for someOtherAsyncFunction is immediately returned. To have the code flow to the catch block, you need to await the promise, like:
async function processResult(data) {
try {
return await someOtherAsyncFunction(data);
} catch (err) {
if (shouldHandleError(err)) {
return;
}
throw err;
}
}
Throwing Errors
Take care not to mix synchronous code with promisified code. For instance, consider this function:
function processResult(data) {
if (!_.isArray(data) || data.length < 2) {
throw new Error('Expected at least two values in result');
}
return promise.resolve(_.map(data, value => dataHelpers.processValue(value)));
}
processResult() returns a promise in the success case, but throws a synchronous error in the other cases, which is bad. Instead, simply mark the function as async which causes a rejected promise to be returned in either case.
async function processResult(data) {
if (!_.isArray(data) || data.length < 2) {
throw new Error('Expected at least two values in result');
If you absolutely need to mix promises with sync values, make sure to use the MaybePromise return type and not to default to an async function, which can create additional overhead if the value is not a promise. Check out the handleMaybePromise helper in the promise section of js-core for how to work with MaybePromise values.