A clone of covid19india.org
Original Codebase: Github
- Use TypeScript instead of JavaScript.
- Use vitejs instead of create-react-app.
- Make it flexible, so that the project can be extended to show many kind of statistics on india.
- To learn how covid19india-react is architected.
- To learn about the react-libraries used. (d3.js, react-spring, etc).
- To learn about the design patterns used, and how they are implemented.
- To learn about how to implement common patterns like dark-mode, multi-language support, data-fetching, lazy-loading etc.
esbuild && vitejs
- created vite app with :
yarn create vite
- WTF are Babel and Webpack 😵 ? Explained in 2 mins.
- We are using
tsc
insted ofbabel
andesbuild
insted ofwebpack
- We are using
- Let's Learn esbuild! (with Sunil Pai) — Learn With Jason
lining and formatting
- Prettier integration with linters
- How eslint and prettier work together with plugins
- ESLint, Prettier & Airbnb Setup
- (Wes Bos) ESLint + Prettier + VS Code — The Perfect Setup
Pre Commit Hooks To lint before commit
-
Build a Modern JS Project - #4 Pre-commit with Husky & lint-staged
-
StackOverflow Fix to
husky
not creating git hooks in.git
directory
Optimization
Immer with useReducer
-
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 theuseReducer
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
-
How to remove blue highlight when you hold on input buttons in css?
-
Side Navbar Desktop -
Position:Fixed
on navbar, andmargin left
on content.
For animationleft: -10rem
, Z-index stacking context as it should look coming from behind. -
Side Navbar Mobile -
Position:Fixed
on navbar.
For animationhight:0vh
toheight:100vh
,margin bottom
for extra height,overflow: auto
so content does not overflow while animation. -
Non Block elements like img, svg have default space in bottom as they were used to show text and some characters like 'g', 'y' go bellow text-baseline. We have to remove that.
-
Transition vs Animation:
transition
requires some trigger like:hover
animation
can self start.transition
binds to a specific css attribute which can animate likeopacity
ortransform
So whenever that field is changed by trigger or by addition or removal of some css class from javascript, it will be automatically animated from old to new value, after css parsing is completed.transition
have only 2 state where asanimation
can have any number ofkeyframes
- How to play an animation on hover and pause when hover is not active
-
What is difference between justify-self, justify-items and justify-content in CSS grid?
-
Flex vs Grid:
- I got a 💝 form Kevin Powell : No justify-self in Flexbox? No problem!
- Notice the '6' didn't got pushed up to below '1'.
- In flex rows align perfectly. but column does not.
- (side note:
flex-warp
works kind of likegrid
,
but you cant set something likefr
unit orflex-grow
in flex on vertical direction i.e on flex-wrap direction ) - So to wiggle the cell up and down within the row, we have
align-items
property. - But as the columns don't lineup like rows do, we don't have
justify-items
property in flex - Though we have
align-content
property withflex-wrap
to control all rows as a complete content. - In
Grid
we havejustify-item
property as in grid unlike flex columns also lineup perfectly.
Justify | Align |
---|---|
React
-
React useEffect can't have async function: React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing
gh-pages with ViteJs
-
How to Deploy Your Vite App to Github Pages // Vue app deployment in 5 quick steps
yarn build
git add dist -f
git commit -m "next version"
git subtree push --prefix dist origin gh-pages
-
Git gh-pages branch and newly created subtree in dist directory does not match git-history
gh-pages with React-router
-
When we provide a url in gh-pages, github considers it as actual url But if we are using a single page application, the url will be stored in the browser history.
-
eg https://ketan-10.github.io/covid19india-react-clone/about/ github will try to find the exact location but actually the location is https://ketan-10.github.io/covid19india-react-clone and /about is in the browser history api.
-
So we add 404.html page which, github will trigger if no url match, and the 404.html will then convert that url to query string and pass to index.html, index.html will decode that query string and append the /about path in history api.
-
React Router 6 : React Router 6 - What Changed & Upgrading Guide
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 insideuseLayoutEffect
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 withTransitionPhase.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
andTransitionPhase.LEAVE
)
and actual new and old data, stored in array call therenderTransitions
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.
- The first input parameter convert it into an array if not already(will discuss next)
-
animation object created by
useTransition
is only understood byanimated.<div|h1|p|...>
component.
and it can even be used multiple times inside the function. -
animated
componentdoes not re-render the react-component
while animating.
It uses something calledreact 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 ofVolunteers
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.
NowuseTransition
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);
- If we have
-
- 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:
-
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 foruseTransition
. -
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
oranimated.h1
etc. - In JSX the following syntax is converted to Following
So basicallydiv
,h1
,p
, are all react components* on Objectanimated
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 inwithAnimated
Function. Which is called by above codeanimated('div')
- NOTE: All the primitives as Components are created on
animated
Object when we importreact-spring
regardless of will use it or not.
Those are created bycreateHost
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.
- When using animations we have to use react component like
-
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 onx
and create animation by updating current component usinggivenRef
.- If we notice, we are directly passing
{ x }
tostyle
props.
Thestyle
prop is considered special react-spring will automatically create Interpolation for it.
it's done by creatingAnimationStyle
object by callinghost.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 toprops.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
- As in the example we have multiple animation for same
-
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 onprop
for this exampleprop.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.
- As we return the same
- Parent Component should not re-render on each animation frame.
- The when Interpolation object will observe the change,
It has thegivenRef
of forwarded ref, so it will directly change the HTML using ref.
- The when Interpolation object will observe the change,
- When Parent Component re-render it should not affect animation state.
Dark Mode
-
use
useDarkMode
hook in your navbar, on toggle it adds css classdark-mode
tobody
, 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
-
Configurations:
- List of i18next Configuration Options
- http-backend Configurations
-
i18next works with plugins to support multiple frameworks, data sources types and language detections.
Code fromi18next.js
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 in3rdParty
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 usesuseTranslation
hook which will internally registers the current hook toi18n.on('languageChanged', (t) => setT(t))
.
Suspense
-
With i18next App component is wrapped with
Suspense
component. : A React component suspended while rendering, but no fallback UI was specified -
Future of Suspense Data Fetching With Suspense In Relay | Joe Savona
-
TODO: experimental will be useless/updated in react18 Concurrent UI Patterns
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
-
About
position: sticky
by kevin powell : A couple of cool things you can do with CSS position sticky -
Not work with
overflow
property set How to fix Issues with css position sticky Not Working -
Github issue on w3c: [css3 positioning] support position:sticky inside an overflow:hidden|auto on general parents
-
Javascript solution: Position: stuck; — and a way to fix it
Typescript
-
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;
}>;
- VS-Code show expanded type: How can I see the full expanded contract of a Typescript type?
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
-
useEffect
runsafter
DOM is rendered on the page or in parallel,
Due to this if weupdate DOM element
in useEffect there we see aflicker
and old position is displayed for a sec.
SouseLayoutEffect
runs before Render, so there will be no flicker. -
If
useLayoutEffect
runs before render. How we able to read the element size?
How does React measure DOM Elements in useLayoutEffect hook correctly before browser gets a chance to paint?
Basically useLayoutEffect runs after Dom have been parsed by the browser but not rendered.
This same Feature is used with native javascript when we update the dom layout, and then read it.
This is calledreflow event
: My findings onreflow-event
andcritical-rendering-path
Miscellaneous
{"ketan":"this",...(true == true && {"hello":"hi"})}