ElPirru
ElPirru

Reputation: 183

Showing a CSS loading indicator while fetching data

I have a indeterminate linear css loading indicator like the ones in material design. Every time I fetch data from a Redux's reducer, I show this indicator. The problem is that while the indicator shows up, it is not being animated. It looks like the whole pages freezes up until the reducer is populated with the data.

Let's suppose I have my loading indicator visible and animated at all times. As soon as I fetch/request data into a reducer, the loading indicator stops animating.

What do you think is causing this? Is it fixable or Do I need to implement a gif indicator and not a css one?

EDIT 1

This is the my Loader component:

import React, { Component, PropTypes } from 'react';
import radium from 'radium';

// indeterminate Linear (type: indeterminateLinear)
const indeterminateLinearLong = radium.keyframes({
  '0%': {
    left: '-35%',
    right: '100%'
  },
  '60%': {
    left: '100%',
    right: '-90%'
  },
  '100%': {
    left: '100%',
    right: '-90%'
  }
});
const indeterminateLinearShort = radium.keyframes({
  '0%': {
    left: '-200%',
    right: '100%'
  },
  '60%': {
    left: '107%',
    right: '-8%'
  },
  '100%': {
    left: '107%',
    right: '-8%'
  }
});
const indeterminateLinear = {
  base: {
    backgroundColor: '#26a69a',
  },
  progress: {
    position: 'relative',
    height: '4px',
    display: 'block',
    width: '100%',
    backgroundColor: '#acece6',
    // borderRadius: '2px',
    backgroundClip: 'padding-box',
    // margin: '0.5rem 0 1rem 0',
    overflow: 'hidden',
    zIndex: '999'
  },
  before: {
    position: 'absolute',
    backgroundColor: 'yellow',
    top: '0',
    left: '0',
    bottom: '0',
    willChange: 'left, right',
    animation: 'indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite',
    animationName: indeterminateLinearLong
  },
  after: {
    position: 'absolute',
    backgroundColor: 'yellow',
    top: '0',
    left: '0',
    bottom: '0',
    willChange: 'left, right',
    animation: 'indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite',
    animationName: indeterminateLinearShort,
    animationDelay: '1.15s'
  }
};

// Indeterminate Cirular (type: indeterminateCircular (default))
const indeterminateCircularRotate = radium.keyframes({
  '100%': {
    transform: 'rotate(360deg)'
  },
});
const indeterminateCircularColor = radium.keyframes({
  '100%, 0%': {
    stroke: 'red'
  },
  '40%': {
    stroke: 'blue'
  },
  '60%': {
    stroke: 'green'
  },
  '80%, 90%': {
    stroke: 'yellow'
  }
});
const indeterminateCircularDash = radium.keyframes({
  '0%': {
    strokeDasharray: '1,200',
    strokeDashoffset: '0'
  },
  '50%': {
    strokeDasharray: '89,200',
    strokeDashoffset: '-35'
  },
  '100%': {
    strokeDasharray: '89,200',
    strokeDashoffset: '-124'
  },
});
const indeterminateCircular = {
  loader: {
    position: 'absolute',
    width: '100px',
    height: '100px',
    left: '50%',
    top: '20%'
  },
  circular: {
    animation: 'x 2s linear infinite',
    animationName: indeterminateCircularRotate,
    height: '24px',
    position: 'relative',
    width: '24px'
  },
  path: {
    strokeDasharray: '1,200',
    strokeDashoffset: '0',
    animation: 'dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite',
    animationName: indeterminateCircularDash,
    strokeLinecap: 'round',
    stroke: 'red',
    strokeWidth: '2'
  }
}

class Loader extends Component {
  render() {
    const {
      type
    } = this.props;
    let loader = null;

    if ( type === 'indeterminateLinear' ) {
      loader = (
        <div style={ indeterminateLinear.progress }>
          <div style={ indeterminateLinear.before }></div>
          <div style={ indeterminateLinear.base }></div>
          <div style={ indeterminateLinear.after }></div>
        </div>
      );
    } else {
      loader = (
        <div>
          <div style={ indeterminateCircular.loader }>
            <svg style={ indeterminateCircular.circular } viewBox="25 25 50 50">
              <circle style={
                indeterminateCircular.path
              } cx="50" cy="50" r="20" fill="none" strokeMiterlimit="10"/>
            </svg>
          </div>
        </div>
      );
    }

    return (
      <div>{ loader }</div>
    );
  }
}

Loader.propTypes = {
  type: PropTypes.string,
};

export default radium(Loader);

When I include the Loader component, it works and animates without a problem; however, when I request some data to be fetched, the animation stops. I can also notice that everything else frozes up too until React rerenders and shows the fetched data.

This Loader component is being included in an top level component that is connected to the reducers, when data is being fetched, the component is being shown.

{ this.props.globalState.someReducer.isFetching ?
  <Theme render="Loader" type="indeterminateLinear" /> : null
}

EDIT 2

I want to add another use case. This time has nothing to do with redux, so we can remove it from the picture. It has to be with react itself.

I have a page listing some items, approx. 40 items, and I have a button that sorts this list by date.

This is what I have in my render function:

const sortDesc = ( a, b ) => (
    transactions[b].updatedAt ||
    transactions[b].createdAt) -
    (transactions[a].updatedAt ||
    transactions[a].createdAt
)
const sortAsc = ( a, b ) => (
    transactions[a].updatedAt ||
    transactions[a].createdAt) -
    (transactions[b].updatedAt ||
    transactions[b]. createdAt
)

let sortDate = sortDesc;
if (!this.state.sortDesc) {
  sortDate = sortAsc
}

<button onClick={ this.handleSortBy }>sort by date</button>

        {
          Object.keys(transactions)
            .sort(
              sortDate
          ).map(( key, index )=> {

            return (
              <div key={ index }>
                <Payout {...this.props} />
              </div>
            );
          })
        }

I have my Loader component inside a parent component. The structure could be something like this:

-- Layout <-- here we have the Loader

---- Transactions <-- here is the sort function

------ Payout <-- child component

Now, everytime I hit the sort button, everything freezes up until the sorting is finished. When I say "everything" it means all components, including parent components (in this case Layout and Loader).

The Loader is built with Radium.

I must be missing something here, I cannot believe all the components will freeze up when a child componen is re-rendering.

Upvotes: 2

Views: 2042

Answers (1)

ElPirru
ElPirru

Reputation: 183

I finally solved it. After some research, I learned a lot about web workers just to find out it was not the right solution. Anyways, it has to do on how javascript works. Javascript is single threaded and thus blocking. So when we have some task that takes some time (in my case was a second or so) everything we seen on screen will freeze, even css animations! It does depend on what browser you are using. In my case, i was using Chrome. I tested it firefox and the animations did not stop.

Let's go to the solution. I noticed that Chrome has some issues with transitions, specially with some basic css elements such as top, right, bottom, left, margins... so i had to go with transforms. Not all transforms will work either. For example, translate percentages won't work, so I had to use pixels. For this, i needed to calculate the width of the parent element and do some calculations to be able to translate my child element on the X axis from the left to the right of the screen.

I will update this post with the actual code.

Upvotes: 1

Related Questions