Skip to content

ketan-10/covid19india-react-clone

Repository files navigation

covid19india-react-clone

A clone of covid19india.org

Original Codebase: Github

Changes from original

  1. Use TypeScript instead of JavaScript.
  2. Use vitejs instead of create-react-app.
  3. Make it flexible, so that the project can be extended to show many kind of statistics on india.

Motivation

  1. To learn how covid19india-react is architected.
  2. To learn about the react-libraries used. (d3.js, react-spring, etc).
  3. To learn about the design patterns used, and how they are implemented.
  4. To learn about how to implement common patterns like dark-mode, multi-language support, data-fetching, lazy-loading etc.

Steps

esbuild && vitejs

lining and formatting

Pre Commit Hooks To lint before commit

Optimization

Immer with useReducer

  • Using Immer with Reducers and React Hooks

  • Carrier Produce i.e. produce function which takes a function as a single parameter.

  • In produce(reducer) immer will convert the function where
    first argument will be replaced with mutable
    We can override the useReducer hook like following

const useImmerProducer = (reducer, initState) =>  {
  return React.useReducer(produce(reducer), initState)
}
  • So instead of writing actions like following
    Before:
const actionReducer = (state, action) => {
  switch(action.type) {
    case SET_COUNT:
      return produce(state, (mutableState) => {
        mutableState.count = action.count
      })
  }
}

After:

const actionReducer = (state, action) => {
  switch(action.type) {
    case SET_COUNT:
      state.count = action.count
      return;
  }
}
  • Inner Implementation:
const produceFunc = (myFun) => {
  return (args) => {
    return produce(args[0], (mutable) => {
      myFun(mutable, args[1:])
    })
  }
}

CSS

Justify Align
1 2

React

gh-pages with ViteJs

gh-pages with React-router

D3.js

Animation

  • A Component renders multiple times, but animation should not happen each time component render.

  • Also when we want to remove the element from the DOM, we cant just remove as there will be no animations.

  • So all this is handled by useTransition this hook constructs a function according to first input parameter on each render:

    • If first-time-render (mounted) return function to animate-IN the element.
    • On re-render if Old input same as new input, do nothing it's just parent component re-render
    • If Old input different from new input, return function to animate-OUT existing element and animate-IN new element if any.
      useRef is used to store the previous animate-OUT components.
  • After animate-OUT the parent component will re-render and destroy animating-out element.

  • useTransition hooks job is not to actually render the animation, but it is as follows:

    • The first input parameter convert it into an array if not already(will discuss next)
      and save it in useRef for later use. save it inside useLayoutEffect so it's saved when the function is complete, i.e at end. (like defer in golang)
    • Check the previous value of first input parameter. it there add it to the transition array with TransitionPhase.LEAVE.
    • There are following transition-phases:
      • TransitionPhase.ENTER
      • TransitionPhase.LEAVE
      • TransitionPhase.UPDATE
      • TransitionPhase.UNMOUNT
    • Read the second input, which is configuration object, and calculate animation object from that(using physics 😄)
    • With the animation object of new and old element data (TransitionPhase.ENTER and TransitionPhase.LEAVE)
      and actual new and old data, stored in array call the renderTransitions function two times, for both old and new element.
    • If the first input value has not changed. Due to component re-render for another update or first time render.
      It will be just one (same) element. so useTransition will not detect any changes.
      and the animation object will have no animations.
  • animation object created by useTransition is only understood by animated.<div|h1|p|...> component.
    and it can even be used multiple times inside the function.

  • animated component does not re-render the react-component while animating.
    It uses something called react forwardRef
    to set/update css style value directly by bypassing react renderer. withAnimated source-code

  • Use Transition for Array of Elements:

    • If we have n-number of animation targets. Like in this case we have array of Volunteers to animate.
      They could be added or removed.
    • We have to make sure we don't just un-mount the removed element from the array.
      As this will mean no transition-out animation.
    • So useTransition supports animate the array of elements.
      We pass the data array to the useTransition first parameter and also pass the key to identify the element.
      Now useTransition determine using key and pervious-input (from use-ref), if the element is new or old.
      and determine the transition-phase for each-data(element) in the array.
    • For each data(element) the function is called with style(animation-object) and element-data from the array.
    • Volunteers Github Gist
    • To test this try to call transition function in the component, when we log the return value it is the ReactFragment containing 2 children. one rendering in and one rendering out.
    const components = navbarTransition((style, item) => {
      console.log('rendering: ', style, item);
      return (
        <animated.div {...{ style }} className="nav-animated-menu">
          Hello
        </animated.div>
      );
    });
    console.log('COMPONENT: ', components);
  • My project using react-spring

  • UseSpring

    • Consider following example
    const TestAnimation: React.FC = () => {
      const [clicked, setClicked] = useState(false);
      const [flip, set] = useState(false);
    
      const { x } = useSpring({
        reverse: flip,
        from: { x: 0 },
        to: { x: 200 },
        x: 1,
        delay: 200,
        config: { duration: 2000 },
        onRest: () => set((flip) => !flip),
      });
    
      const getNumber = () =>
        x.to((n) => (clicked ? n.toFixed(0) : n.toFixed(2)));
    
      return (
        <>
          <animated.div style={{ x }}>{x.to((n) => n.toFixed(1))}</animated.div>
          <animated.div style={{ x }}>{getNumber()}</animated.div>
          <button type="button" onClick={() => setClicked((c) => !c)}>
            {clicked.toString()}
          </button>
        </>
      );
    };
    
    export default TestAnimation;
    • Which creates following output:

    CPT2203012016-432x60

    • Objectives:

      • When Parent Component re-render it should not affect animation state.
      • Parent Component should not re-render on each animation frame.
    • I think of useSpring as building block for useTransition.

    • UseTransition works with data or data array, using data binding.
      By keeping track of pervious data and add or remove animation accordingly

    • Where as in useSpring we can define when animation to start and stop.

    • Use Spring hook returns an SpringValue object.
      Which is memoized, so does not change on re-render.
      and we can add multiple observers to it, for animation.

    • If you check when x value changes, it does not change on re-render.

      useEffect(() => {
        console.log('X is changed : ', x);
      }, [x]);
  • Animation.[div|h1|...] Component

    • When using animations we have to use react component like animated.div or animated.h1 etc.
    • In JSX the following syntax is converted to Following
      So basically div, h1, p, are all react components* on Object animated
      Here it is added in react-spring source code, And Here it is the list of all primitives
    • In source animated.div is created with this code in withAnimated Function. Which is called by above code animated('div')
    • NOTE: All the primitives as Components are created on animated Object when we import react-spring regardless of will use it or not.
      Those are created by createHost in index file only.
    • All this components are created as a Forward Ref so we can access the direct reference of html element to animate.
  • Animation.div inputs and Interpolation object

    • As in the example we have multiple animation for same x object.
    • x.to((n) => n.toFixed(1)) this line returns an Interpolation object.
      It is an Observer which will observer on x and create animation by updating current component using givenRef.
    • If we notice, we are directly passing { x } to style props.
      The style prop is considered special react-spring will automatically create Interpolation for it.
      it's done by creating AnimationStyle object by calling host.createAnimatedStyle(props.style) on each render.
    • In The example we are passing x.to((n) => n.toFixed(1)) directly as a children.
      If we pass Object as a child to Custom Component, it goes to props.children. Which react-spring will use to create animation.
    • On each render, new Observers will be attached and previous Observers will be unsubscribed. It is done by keeping the lastObserver in ref, and useEffect for each render. Source Code
  • Note:

    • As per example Following works
    <animated.div>{x.to((n) => n.toFixed(1))}</animated.div>
    • But the following does not:
    <animated.div> Hello {x.to((n) => n.toFixed(1))}</animated.div>
    <animated.div></div>{x.to((n) => n.toFixed(1))}</div></animated.div>

    As the Interpolation object outside the style prop,
    will only work if it is set directly as an object on prop for this example prop.children.

  • Takeaways in Objectives:

    • When Parent Component re-render it should not affect animation state.
      • As we return the same x object (SpringValue) each time, the object will not change so does the animation state.
      • The Interpolation Observers (x.to((n) => n.toFixed(1))) are created from the current state of SpringValue(x) Each render,
        And the previous observers are un-subscribed each render.
    • Parent Component should not re-render on each animation frame.
      • The when Interpolation object will observe the change,
        It has the givenRef of forwarded ref, so it will directly change the HTML using ref.

Dark Mode

  • useDarkMode stop first time flash when page loads

  • use useDarkMode hook in your navbar, on toggle it adds css class dark-mode to body, we can use this class in css to change colors with !important

  • will re-render just the components where it's used, it uses use-persisted-state to achieve this, which intern uses DOM event listeners to call others components, and share update with multiple hooks.

i18next

const use = (module) => {
  if (module.type === 'backend') {
    this.modules.backend = module;
  }

  if (module.type === 'logger' || (module.log && module.warn && module.error)) {
    this.modules.logger = module;
  }

  if (module.type === 'languageDetector') {
    this.modules.languageDetector = module;
  }

  if (module.type === 'i18nFormat') {
    this.modules.i18nFormat = module;
  }

  if (module.type === 'postProcessor') {
    postProcessor.addPostProcessor(module);
  }

  if (module.type === '3rdParty') {
    this.modules.external.push(module);
  }

  return this;
};
  • Backend : how to fetch the translations from a server or file.
    We can also specify multiple backends for fallback.

  • Language Detector : how to detect current language
    Considers client location, cookies and query string.

  • i18next provides functionality to register a callback to be called when the language changes.
    But we want to re-render react components on language change, and want to use easy hooks to do that.
    For that we use i18next-react a plugin categorized in 3rdParty plugin.

    by i18n.use(initReactI18next) we pass the i18n instance to react-i18next
    which will make it available for all the components via the context api.

  • i18next-react uses context API to pass the i18n configurations to the i18next form component and vice versa. But we have used locales.ts file for that.
    To Detect changes for re-render i18next-react uses useTranslation hook which will internally registers the current hook to i18n.on('languageChanged', (t) => setT(t)).

Suspense

Data Fetching

  • SWR (state-with-rehydration) is a data data fetching and caching library which uses cache invalidation strategy.
    SWR first returns the data from cache (stale), then sends the request (revalidate), and finally comes with the up-to-date data again.

  • When link change keep the old result and don't return undefine: Keep previous result while revalidating

Position: Sticky

Typescript

  • My Typescript Notes

  • Typescript checks are all only compile time
    It's easy to know at first glance but also easy to forget
    i.e we cant have variables in type we have to use typeof

  • Utility types: Extract
    Custom union type

type ObjectUnionType<T> = T[keyof T];

const one = {
  a: 1,
  b: 'ketan',
  c: true,
};

const two = one as { [P in 'a' | 'c']: typeof one[P] }; // pick -> https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys

// typeof two
const two: {
  a: number;
  c: boolean;
};

const myVar = 'a' as keyof typeof one;
// typeof myVar
const myVar: 'a' | 'c' | 'b';

// following are same:
const three = one[myVar] as typeof one[keyof typeof one];
const four = one[myVar] as ObjectUnionType<typeof one>;

// typeof three
const three: string | number | boolean;
// typeof four
const four: ObjectUnionType<{
  a: number;
  b: string;
  c: boolean;
}>;
type test = SomeFunction;
type test = Expand<SomeFunction>;
// expands object types one level deep
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
// expands object types recursively
type ExpandRecursively<T> = T extends object
  ? T extends infer O
    ? { [K in keyof O]: ExpandRecursively<O[K]> }
    : never
  : T;
const animals = ['cat', 'dog', 'mouse'] as const;
type Animal = typeof animals[number];

Reflow-Event & Critical-Rendering-Path & UseLayoutEffect - Updating the DOM element size without the flicker

Miscellaneous

  {"ketan":"this",...(true == true && {"hello":"hi"})}

About

A clone of covid19india.org using Typescript and a few other changes.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published