Category Archives: Frontend

Not every kind of state needs to go to Redux

Only some kinds of state need to be in Redux,

  • State that’s shared by multiple components on the same page
    • State that’s read by multiple components
    • State that’s written by one component and read by another
  • State whose lifecycle is longer than its component. For example, “recordList” for a list page – If you go to a detail page from a list page, and then come back, you don’t want to re-fetch the list from the backend. With “recordList” in redux you can just read it from the Redux store.

Typical cases that you don’t need Redux for you state:

  • Form values – And this is why Formix is better than Redux-Form
  • “currentRecord” for a detail page

Best practice of html5 input for limited length

What you need is ,

  • Only numbers can be input
  • The length of input is limited to N, say 2
  • On mobile devices, a number pad will be shown

And the way to do it is:

<input type="number" pattern="[0-9]*" oninput="this.value=this.value.slice(0,2)"/>

If you are using material-ui, it will be

<TextField
    inputProps={{
        type: "number",
        pattern: "[0-9]*" 
    }}

    onInput={(e: any)=>{
        e.target.value = e.target.value.slice(0,2)
    }}
/>

Opinion: Don’t use class for data structure in typescript (2019). Use interface only

Always use interfaces instead

Problems of class:

  • Lots of frameworks such as Redux don’t support class instances. They only support plain objects. There are tools that can convert plain objects to class instances, but the point below makes it awkward.
  • Given a variable “someType: SomeType” in the code, you don’t know if it is a plain object or a class instance. So you don’t know if you can call its methods, or if it is OK to call plainToClass() to convert it to a class instance.

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
    )
);