My React takeaways (2020 April~August)
Tags: react, javascript
Published: 2020-08-05 18:24:29

## Multi-API fetching React project structure with profuse usage of Redux

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dox4xpud8oksmz8xasnc.jpg)
_The original outline_

The site is not finished yet, with a lot of tedious work left consisting of parsing and displaying blockchain transactions, but the general structure which I had envisioned at the beginning of working on the project is pretty much visible, plausible and most importantly criticizable.
~~Here are the dos, donts, trys and takeaways.~~ So this was the original intent of writing this, but it just ended being a bunch of random tips and takeaways that I amassed while working on this project.

### Do - learn how to use RxJS. 
But only after you realize that RxJS is truly a god-send in writing readable, maintainable, concise multi-asynchronous code. The project I was working on was I believe to be a really rare specimen of a project, where using RxJS could drastically improve DX(Developer Experience). Simple projects where asynchronousity is not too abundant should work wonders with fine-tuned custom hooks to suit your needs. I admit that I probably haven't even scraped the surface of the true power and paradigm-shifting utility of RxJS, but a bare understanding and skim through the operators was enough for me to realize that learning RxJS was like learning React after just using javascript.

### Try - Managing all APIs, routes and retrieved data refining functions in a separate location/file

```javascript
const refiner = {  
  default: new Map(),  
  ...getInitStatePerChain(new Map()),  
};  
  
_.each(_.keys(chains), v => (refiner[v] = new Map()));  
const {  
  REDUX: {PERIODICS, DATA},  
  DB,  
} = consts;  
  
//  /status  
const statusProps = ["block_height", "block_time", "total_validator_num", "unjailed_validator_num", "total_supply_tokens", "bonded_tokens", "total_txs_num"];  
refiner.default.set(PERIODICS[0].name, data => {
	//	pick values and refine
}

//	.....
//	a lot of refiners
//	....

  
export const apiRefiner = (chain, name, data) => {  
  if (!_.isMap(refiner[chain])) throw new Error(`CRITICAL - chain ${chain}'s refiner is non-existent`);  
  const refinerFunc = refiner[chain].has(name) ? refiner[chain].get(name) : refiner.default.get(name);  
  if (!_.isFunction(refinerFunc)) throw new Error(`CRITICAL - default refiner function for ${name} is non-existent`);  
  return refinerFunc(data);  
};
```


If I had to pick the one thing that I tried out that probably increased the most productivity, it was this.
Manage all routes in a config file, manage all data refining by defining a map, and mapping the route to a refining function which is used to refine all data retrieved from said refiner. This method has several pros and minimal cons.
- Pro - Using the same API in multiple places couldn't be easier(Modularizing the whole thing and calling the API using a custom hook turned the whole process into a single line of code + defining values in the config)
- Pro - No need to use postman or any other API displaying tools(mostly), it's all nicely tucked inside your refiner file
- Pro - API related logic apart from using the data gets completely detached from your components/containers
- Con - initial setup takes some time

Actually using it would look something like this in a custom hook
```javascript
React.useEffect(() => {  
  if (empty(key) || timestamp !== 0) return;  
  const targetUrl = `${consts.ENV[chain].api}${dbObj.route(key)}`;  
  simpleGet(targetUrl)  
	  .then(res => {  
		  // console.log(res);  
		  sort(res.data).desc(v => v[idKey]);  
		  const refinedData = apiRefiner(chain, dbObj.name, res.data);  //	refiner
		  setData(refinedData);  
		  const docs = _.map(refinedData, v => makeDoc(v, v[idKey]));  
		  db.lazyBulkPut(docs);
	  })  
	  .catch(ex => {  
	  console.warn(`error during fetchToDB - ${targetUrl}`, ex.message);  
	  setError(true);  
	  });  
}, [key, setData, setError, data, chain, timestamp, db]);
```

which leads to the next takeaway.

### Hooks are awesome. I recommend migrating and never looking back.
The one thing that I found inconvenient was not having a componentShouldUpdate and ErrorBoundaries.
But after getting used to the ['proper way' of using useEffect](https://overreacted.io/a-complete-guide-to-useeffect/)(which I frequently stray from btw), and almost compulsively wrapping everything that I can get my hands on with useCallback or useMemo, missing componentShouldUpdate became pretty much trivial. As for ErrorBoundaries... well let's [leave that as a con](https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes) :)

### Do - take care of object reference equality & in-equality
So a certain component was rerendering way too many times even before it had any kind of value. Turned out I had given it a default value of `{}` which was initialized somewhere else, resulting in a new object every time, thus causing the rerender. Object reference in-equality.
Ever since that experience, I created the following constant in my `consts.js` file, and in scenarios where initializing to an object was needed, I simply used that instead.
```javascript
consts = {
//	...
MISC: {  
  DEFAULT_OBJ: {},  
  DEFAULT_ARR: [],
  }
//	...
}
```

### Do use Reselect
On a heavily populated, memoized component with a useSelector hook, try `console.count`ing inside the useSelector.
In my case, I saw the number shoot up to 80 before any content appeared to be painted which forced me to learn reselect. It took its toll, but to no regrets. I realized it was pretty much the 'memoized refiners' for selectors.
One thing that I felt was somewhat uncomfortable was that it was recommended in the reselect docs to pass parameters to reselect via redux, forcing me to create a new store that just handled variables I needed to pass. But still worth the hassle.

### A neat real-time wrapper component
```javascript
import React from "react"; 
import {interval} from "rxjs";
 
const source = interval(1000);
export default function RealTime({value, generator = () => null}) {  
  const [, updateState] = React.useState(true);  
  const forceUpdate = React.useCallback(() => updateState(v => !v), []);  
  
  React.useEffect(() => {  
  const subscribe = source.subscribe(() => forceUpdate());  
  return () => subscribe.unsubscribe();  
  }, []);  
  return <>{generator(value)}</>;  
}
```
Another reason to use RxJS. Make the generator do some kind of calculation that changes in time, and voila, you have a component that is real-time, and will be in sync with all other real-time wrapped components.

### Switch css with js instead of React if possible and plausible
```javascript
export default function () {  
  document.documentElement.style.setProperty("--bgHeaderColor", "linear-gradient(to right, #272538, #35305e 81%)");  
  document.documentElement.style.setProperty("--chainNameColor", "#ffffff");  
  document.documentElement.style.setProperty("--color-main", "#9c6cff");  
  document.documentElement.style.setProperty("--bgDashImage", "var(--bgDashCosmos)");
  //	...
}
```
Define variables in your scss / css and change the values using javascript to switch themes / change appearances. Memoize the jsx and you've saved a rerender from repainting the entire component.(Memoizing with accurate dependencies works as well, but this method is so much more straight-forward and easy)
Example in React
```javascript
const [smallSearch, setSmallSearch] = React.useState(false);  
React.useEffect(() => {  
  if (smallSearch) document.documentElement.style.setProperty("--searchHeight", "47px");  
  else document.documentElement.style.setProperty("--searchHeight", "0px");  
}, [smallSearch]);
```
AFAIK, this method takes much less energy than removing and adding elements to the dom.


### Dont - over-cache data
Apart from extreme scenarios - which I thought mine was - high-intensity data caching takes too much time compared to the productiveness of the output. Since I was handling blockchain data which basically means fetched chain data will pretty much never change, it felt perfectly sane to try shoving every bit of data in a DB and checking and retrieving the data from the DB if it existed before attempting to fetch.
I believe the costs far out-weighed the results. It is ultra-difficult, forcing you to build different custom hooks for all kinds of asynchronous scenarios. In hindsight, it might have been worth it if I was well versed in RxJS, but attempting the feat with mostly custom hooks was not my Sunday cup of tea.


Hopefully, this helps someone. Cheers.