Saturday 5 January 2019

Debugging Create React App javascript and tests in Visual Studio Code

This is a handy collection of configurations for debugging your Create React App javscript code (launching Chrome) and also tests generated with Create React App (CRA). Note that the last one does not work probably if you have ejected the CRA. Here is the .vscode/launch.json file:

{
 // Use IntelliSense to learn about possible attributes.
 // Hover to view descriptions of existing attributes.
 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 "version": "0.2.0",
 "configurations": [
  {
   "name": "Chrome debug 3000",
   "type": "chrome",
   "request": "launch",
   "url": "https://localhost:3000",
   "webRoot": "${workspaceRoot}/src"
  },
  {
   "name": "Debug CRA Tests",
   "type": "node",
   "request": "launch",
   "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
   "args": [
    "test",
    "--runInBand",
    "--no-cache"
   ],
   "cwd": "${workspaceRoot}",
   "protocol": "inspector",
   "console": "integratedTerminal",
   "internalConsoleOptions": "neverOpen"
  }
 ]
}

Friday 28 December 2018

Debugging http Node.js server requests and responses from shell

Here is a cool tip to inspect Node.js running an http process / server. First off, we start a simple http server in Node.js like this:

const http = require('http');

const server = http.createServer((req, res) => {
    res.end("

Why hello world!

\n"); }); server.listen(4242, () => { console.log("Server is running..."); });
This sets up a http server in Node.js. The http module is built-in in Node.js, we only have to require it (import in ES6). Now, just enter the following (I use Windows Subsystem for Linux - WSL in this case) in your shell to export NODE_DEBUG environment variable: export NODE_DEBUG=http We can now see requests and responses in our Node.js server! (Image below uses Wsl-Terminal as our terminal against WSL).

Sunday 23 December 2018

Canceling Promise in Javascript

Canceling Promise in Js is a often sough after functionality, that can be provided by wrapping the Promise async function and provide canceling abilities. This will in functionality be similar to what we can do in C# with a CancellationTokenSource using in System.Threading.Task objects. We can invoke asynchronous function in Js with Promise, but if the user navigates away from a View or Page in for example React Native component, clicking a button to go to another Component, we must tidy up already started Promise operations such as fetch and here is the code to achieve that. First off, we define and export a makeCancelable method to be able to cancel a Promise.

/**
 * Wraps a promise into a cancelable promise, allowing it to be canceled. Useful in scenarios such as navigating away from a view or page and a fetch is already started.
 * @param {Promise}   promise           Promise object to cancel.
 * @return {Object with wrapped promise and a cancel function}
 */
export const makeCancelable = (promise) => {
    let hasCanceled = false;

    const wrappedPromise = new Promise((resolve, reject) => {
        promise.then(value => hasCanceled ? reject({ isCanceled: true }) : resolve(value),
            error => hasCanceled ? reject({ isCanceled: true }) : reject(error)
        );
    });

    return {
        promise: wrappedPromise,
        cancel() {
            hasCanceled: true;
        }
    };

};

The promise is wrapped with additional logic to check a boolean flag in a variable hasCanceled that either rejects the Promise if it is canceled or resolves the Promise (fullfils the async operation). Returned is an object in Js with the Promise itself in a primise attribute and the function cancel() which sets the boolean flag hasCanceled to true, effectively rejecting the Promise and rejecting it. Example usage below:

'use strict';

import React, { Component } from 'react';
import { TextInput, Text, View, StyleSheet, Image, TouchableHighlight, ActivityIndicator, FlatList, AsyncStorage } from 'react-native';
import AuthService from './AuthService';
import { makeCancelable } from './Util';

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#F5FCFF',
        flex: 1,
        paddingTop: 40,
        alignItems: 'center'
    },
    heading: {
        fontSize: 30,
        fontWeight: '100',
        marginTop: 20
    },
    input: {
        height: 50,
        marginTop: 10,
        padding: 4,
        margin: 2,
        alignSelf: 'stretch',
        fontSize: 18,
        borderWidth: 1,
        borderColor: '#48bbec'
    },
    button: {
        height: 50,
        backgroundColor: '#48bbec',
        alignSelf: 'stretch',
        marginTop: 10,
        justifyContent: 'center'
    },
    buttonText: {
        fontSize: 22,
        color: '#FFF',
        alignSelf: 'center'
    },
    error: {
        fontWeight: '300',
        fontSize: 20,
        color: 'red',
        paddingTop: 10
    }
});

const cancelableSearchRepositoriesPromiseFetch = makeCancelable(fetch('https://api.github.com/search/repositories?q=react'));

class LoginForm extends Component {

    constructor(props) {
        super(props);

        this.state = {
            showProgress: false,
            username: '',
            password: '',
            repos: [],
            badCredentials: false,
            unknownError: false,
        };


    }

    onLoginPressed() {
        this.setState({ showProgress: true });

        var reposFound = [];

        var authService = new AuthService();

        authService.login({
            username: this.state.username, password: this.state.password
        }, (results) => {
            this.setState(Object.assign({ showProgress: false }, results));

            if (this.state.success && this.props.onLogin) {
                this.props.onLogin();
            }

        });




        cancelableSearchRepositoriesPromiseFetch.promise.then((response) => { return response.json(); })
            .then((results) => {
                results.items.forEach(item => {
                    reposFound.push(item);
                });
                this.setState({ repos: reposFound, showProgress: false });
            });
    }

    componentWillMount() {
        this._retrieveLastCredentials();
        cancelableSearchRepositoriesPromiseFetch.cancel();
    }

    _retrieveLastCredentials = async () => {

        var lastusername = await AsyncStorage.getItem("GithubDemo:LastUsername");
        var lastpassword = await AsyncStorage.getItem("GithubDemo:LastPassword");
        this.setState({ username: lastusername, password: lastpassword });
    }

    _saveLastUsername = async (username) => {
        if (username != null) {
            await AsyncStorage.setItem("GithubDemo:LastUsername", username);
        }
    }

    _savePassword = async (password) => {
        if (password != null) {
            await AsyncStorage.setItem("GithubDemo:LastPassword", password);
        }
    }

    componentWillUnmount() {

    }

    render() {


        var errorCtrl = <View />;

        if (!this.state.success && this.state.badCredentials) {
            errorCtrl = <Text color='#FF0000' style={styles.error}>That username and password combination did not work</Text>
        }

        if (!this.state.success && this.state.unknownError) {
            errorCtrl = <Text color='#FF0000' style={styles.error}>Unexpected error while logging in. Try again later</Text>
        }

        return (
            <View style={styles.container}>
                <Image style={{ width: 66, height: 55 }} source={require('./assets/Octocat.png')} />
                <Text style={styles.heading}>Github browser</Text>
                <TextInput value={this.state.username} onChangeText={(text) => { this._saveLastUsername(text); this.setState({ username: text }); }} style={styles.input} placeholder='Github username' />
                <TextInput value={this.state.password} textContentType={'password'} multiline={false} secureTextEntry={true} onChangeText={(text) => { this._savePassword(text); this.setState({ password: text }); }} style={styles.input} placeholder='Github password' />
                <TouchableHighlight style={styles.button} onPress={this.onLoginPressed.bind(this)}>
                    <Text style={styles.buttonText}>Log in</Text>
                </TouchableHighlight>
                {errorCtrl}
                <ActivityIndicator animating={this.state.showProgress} size={"large"} />
                <FlatList keyExtractor={item => item.id} data={this.state.repos} renderItem={({ item }) => <Text>{item.full_name}</Text>} />

            </View>
        );

    }


}

export default LoginForm;