Category Archives: Frontend

Enable google analytics with react-router

One way of doing this is to create your own Router class, which extends the one from the framework; and inside this Router class you put in the tracking logic:

class MyRouteComponent extends React.Component<Props, State> {

    constructor(props: Props) {
        super(props);
        this.state = {currentUserRefreshed: false};

        if (isProdEnv()) {
            ReactGA.initialize('UA-GOOGLE-ANAYLTICS-ID');
        }
    }

    trackPage(pageUrl:string) {
        ReactGA.set({page: pageUrl});
        ReactGA.pageview(pageUrl);
    }

    toPageUrl(location: Location) {
        return location.pathname + location.search;
    }

    componentDidMount() {
        this.props.refreshCurrentUser();

        if(isProdEnv() && this.props.location){
            this.trackPage(this.toPageUrl(this.props.location!));
        }
    }

    render() {
        ...
    }
}

My first react HOC + Redux component in Typescript

The logic: Show a component only if the user logged in

import React from 'react';
import {connect} from "react-redux";

interface AuthenticatedUserOnlyProps {
    authenticated: boolean
}


function mapStateToProps(state: State) {    
    return {
        authenticated: state.authenticated
    };
}

export function authenticatedUserOnly<T>(WrappedComponent: React.ComponentType<T>) {
    class FinalComponent extends React.Component<AuthenticatedUserOnlyProps > {
        public render() {

            const {authenticated, ...otherProps} = this.props;
            if (authenticated) {
                return <WrappedComponent {...(otherProps as T)}/>
            } else {
                return null;
            }
        }
    };

    return connect(mapStateToProps, null)(FinalComponent);
}

To use it, just

render() {
    const someComponent = authenticatedUserOnly(SomeComponent); 

    return {
        <div>
           {someComponent }
        </div>
    }
}

IOS Safari showing a blank page when coming back to a React Component

Problem

When you go to a react component, then go to another, and finally come back, the react component is showing blank, or half-blank.

Investigation

If you inspect the page with the mobile safari inspector , you can see the elements are there, but safari just refuse to show the data on the screen.

In my case, this seems to be related with data reloading when I come back to the component. It reloads the data in its componentDidAmount() then re-render. Safari may not perform well enough to handle this kind of re-rendering.

Fix

A workaround is to not reload the data when you come back to the component. You can do this by checking if the data exists, and only loads the data if not existing.

It comes with a price: when you come back to the component, you are not seeing the data that’s up-to-data. There are a few things you can do to make it up:

  • A delayed data re-loading
  • Always reload the data 24 hours (e.g.) after last data loading
  • Very important: destroy the data if a user logs out to make sure a different user won’t see the first user’s data

Change the font family globally in Material-UI 3.x

Some say you should use the jsx-global-plugin like “@global body{fontFamily:’someFont’}” , but it doesn’t work in my project. The global CSS still has the lower CSS speciality than MUI components’ CSS because this global CSS is using a type selector.

You should use a theme instead

//create a theme 

const myTheme = createMuiTheme({
    typography: {
        fontFamily: "Roboto Condensed" 
    }
}


// use the theme

class MyComponent extends React.Component<Props> {

	... 
    render() {
        
            return (
                    <MuiThemeProvider theme={this.props.theme}>
                        <CssBaseline/>
                        <p>Some content</p>
                    </MuiThemeProvider>
                );
        
    }
}



Redux + Typescript: Use class to define Actions

You want the actions to be typed. You should use interface or class.

Class is better than interface because in interfaces you cannot define the action "type" as a constant, but in classes you can:

class LoadingStartedAction implements Action{
    readonly type = LOADING_STARTED
}

However a class instance is not a plain object, but Redux requires plain objects to be dispatched.

This can be handled by a middleware which converts the class instances to plain objects

// See https://github.com/reduxjs/redux/issues/992#issuecomment-167964652
const typedActionToPlain = (store: any) => (next: any) => (action: any) =>  {
    next(Object.assign({}, action));
};

export default typedActionToPlain;

Pay attention not to step into the toe of Thunk middleware when registering it:

const rootStore = createStore(rootReducer,
    applyMiddleware(thunkMiddleware,
        typedActionToPlain //this must be after thunk, otherwise it will try to convert an async function to a plain object
    )
);

Redux + Typescript: The type of the root state

You can get the type of the state from the root reducer

// the reducer
const appReducer = combineReducers({
    global: globalStateReducer,
    example: exampleReducer
});

type AppState = ReturnType<typeof appReducer>

However the redux state can be cleared in some scenarios, so it should be undefinable

//See https://stackoverflow.com/a/35641992/301447 for "undefined redux state" 
type RootState = AppState | undefined;


const rootReducer = (state:RootState, action: Action) => {
    if (action.type === DESTROY_REDUX_STATE) {
        state = undefined;
    }
    return appReducer(state, action)
}

And mapStateToProps() of a component will look like this:

function mapStateToProps(rootState: RootState) {
    return {
        example: rootState!.example  //note the exclamation mark as rootState can be undefined 
    };
}

React Component: A pair of componentDidMount() and componentWillUnmount() may cause infinite loop if error is thrown from render

The following will cause an infinite loop. The render() method will be called again and again.

class ToDoItemEditComponentContainer extends React.Component {
    componentDidMount() {        
        this.props.loadItem();  //load item remotely and put it in the local redux store      
    }

    componentWillUnmount() {
        this.props.clearItemLocally(); //remove item from local redux store
    }

    render() {
        throw "some error" 
    }
}

Here is why: "As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree. " (See here). To be more specific,

  • render() throws an error
  • The react component tree is unmounted
  • componentWillUnmount() is called. And something is cleared from the reduxt store or react state
  • The component tree will be rendered again since some state has been changed

To fix: Implement componentDidCatch() hook in this component or create a global error boundary as shown in here . When there is a render() error, do not call the render() method any more, or do not change any local data in componentWillUnmount()