Search code examples
reactjstypescriptreact-context

React / Context API / TypeScript : How to init web app and avoid UI flickering at statup?


When a user navigate to my site, I need to initialize the React web app as follow:

  1. If the incoming user has never requested the web app, I want to set the UI language to browser default (navigator.language).

  2. If the incoming user has already visited the site and chosen a prefered language (lang stored in the localStorage), I want to init the UI with this language.

  3. If the incoming user has an account and is already connected (token available in localStorage), I want to auto-connect him and render the app accordingly : login button transformed into a welcome message, UI language set to user preference.

To do so, I'm using React Context API and a defaultUser object.

defaultUser: init a default user

const defaultUser = {
  language: 'en_EN',
  isConnected: false
}

Context: create a default context

export const AppContext = createContext({
  connectedUser: defaultUser,
})

Provider: create the provider with default context

export function AppProvider({ children }: any) {

  [...]

  const provider = {
    connectedUser
  }

  return (
    <AppContext.Provider value={provider}>
      {children}
    </AppContext.Provider>
  )
}

App: init the provider during app start up

export class App extends Component {

  static contextType = AppContext

  render() {
    return (
      <AppProvider>
        <AppContainer />
      </AppProvider>
    )
  }
}

AppContainer: render the app

export class AppContainer extends Component {

  static contextType = AppContext

  componentDidMount() {
    /** If user is not connected, verify if a stored session exists and use it to connect user */
    if (!this.context.connectedUser.isConnected) {
      [...do things...]
    }
  }

The whole mecanism works well except an annoying thing : the web app is systematically initialized with default user values, until the AppContainer::componentDidMount() do the real init job. This is causing a sort of flickering effect.

I'm struggeling for 2 days on how to fix that, trying to perform Context init before <AppContainer /> rendering, and I'm stuck.

Any recommandations?

EDIT : For clarity, I'm adding a diagram. Currently :

  1. React App is rendered at start.
  2. Context is initialized at start with default value.
  3. Context is updated when end is reached.
  4. React App is rendered again when end.

Any layout change during these two steps (UI language, UI modification based on user permissions) are clearly visible to the user and generate a sort of flickering. Sequence

I found sort of a solution by simply conditionning <AppContainer/> loading, postponing it to the end of the sequence. However instead of having flickering I have now a lag and other unwanted side effects.

The goal would be to differ all the sequence before React Act is rendered, and after Window is available. Then dynamically create the Context, then render all.

I think the point would be resolved if I could dynamically create the AppContext and pass a variable to createContext() during App constructor() or maybe componentWillMount() (not sure when Window is available), but then TypeScript get into play with types issues and I'm still stuck.


Solution

  • You didn't share the code that initializes the context, but I suspect you put the default value to be either a hardcoded value, or navigator.language and therefore experience the flickering. I'd like to suggest two ways to solve this:

    Solution 1

    Perhaps instead of having a hardcoded default context you could generate the default context programmatically by accessing localStorage.get('lang') or similar? There is a slight drawback to this solution though: You will be mixing concerns of react and the browser, but I think in this case it's an alternative to consider, because it's very simple and obvious to the reader.

    Solution 2

    Alternatively, when calling ReactDOM.render you could pass down whatever you need from localStorage as a prop to your application and so you keep the browser related logic separate from the pure React stuff.

    I hope this makes sense.