# DaoHaus JS Code Guide ### Goals - Organize the app to make it easier to debug, maintain, and integrate new functionality - Create a common code paradigm used by all devs on the team. - Share the rules of this paradigm to make it easier for new devs to contribute lasting code. - Make code more consistent and readable - Make code easier to debug. ### Overarching Ideals - Less code. Perform the greatest amount of work with the least amount of expression. - Less state. Strive to eliminate excess state wherever possible. - DRY - Functional is generally preferred over Object Oriented. - No hard rules. Devs should be free to break any rule at any time. However, if they do wish to break a rule, they should outline their reasoning in a comment. - Any major changes or new patterns should be posted in #code-review on Discord - Clean as you go. Devs rarely refactor code months later, despite their best intentions. So it's best to deliver clean code from the outset. - Criticism and review should be frequent and impersonal. - This is an ongoing process. We need to debate and discuss these rules in order to improve them. ## All JS - Prefer pure array methods over for loops - When iterating over objects, either convert to arrays and use the rule above or use a 'for in' loop enclosed in its own function. - Ternary operators should be limited to two conditions. It becomes messy fast if we chain these together. Refactor function calls to 'if' blocks or switches for complex cases. - Prefer unary operators where applicable (In cases where .length is being tested for in a JSX return block, use a .length > 0 to prevent a random zero from showing up in the UI) - Prefer constants and multiple returns over reassigning variables. - Prefer currying, callbacks, and higher order functions over classes and big objects. - Return null instead of undefined. ## Caching We use a lot of cache unfortunately. Not only is cache the cause of many bugs, it can actually create a situation where the app permanently 'breaks' for the user. Here are some rules to prevent the worst of caching bugs. **Ask these questions before caching** - Could the user come back and want to see this data again? (ex. Hub view) - Does it take long to load this data (ex. massive graph or API fetch)? - Is the user unlikely to change this data before their next refresh or contract TX? If yes to all these, then use cache. Likely sessionStorage. **When to use local storage vs. session storage** - If the data is constant, and will never change (ex. *hasUserLoggedIn* for auto-login features), then use local storage - If the data is likely to change eventually, but not likely to change before the user refreshes the page or fires a contract TX, then use Session storage (ex. the DAOs a user belongs to) - Note: Our session storage empties on refresh. TXs update all state, usually refreshing cache values. We also have a utility that empties cache if it goes over the browser limit (usually 5mb). **After using cache** - Document in code review when used - Try to test edge cases. - Think of how cache might cause state to 'stick'. ## Async/Await - All async/await calls should be wrapped in a try/catch should have - All promise/.then calls require a .catch ## React Structure - Use JS to handle complex logic and state, not JSX. - Components should simplify as they spread out from the app's root. - Prefer many small and stateless components to React behemoths - Hold data in state only if 100% necessary. - Scope state only to where it needs to be used. - While using useState, it's cleaner to separate state into their own concerns than it is to keep it all in one object. - Lazy load independent branches of the app (Once updated). - Error boundaries should become the norm. (Once updated). - Use context when state is required over multiple views or pages. Scope minimally. - If initializing useState with a deliberate falsy value, use null instead of undefined. ## Components - It should be easy to understand the rendering logic of a React Component. Something people can understand fully after 5 minutes of reading. If it isn't, refactor until it is. - Refactor pure functions to outside of the component (so they don't get redefined on every render, and to stop useEffect trouble). - Refactor complex logic to utilities. - Component length should be capped at 250 lines - Stateful components should have their own file - Non stateful components can be in the same file if they are used exclusively by their parent component - Mapping components requires a key. Do not use index for that key as it confuses the React diffing algorithm and causes rendering/performance problems ### useEffect - Include all dependencies in the dependency array - If logic inside of a useEffect calls a function, either: - have that function call a useCallback and include each dependency of that function in the useCallback's dep-array - include the function inside of the useEffect itself - Or refactor the function to outide the component - Do not force the useEffect to run. Istead have the useEffect listen to the dependency and render accordingly. - Check to see how often the useEffect is running. If it sets state or runs complex logic, create guards to escape if it doesn't need to run. - If the useEffect sets a value in state based on synchronous props, consider using useMemo to save that value directly inside the component instead. - Always unsubscribe from listeners - Unsubscribe from asynchronous calls (not currently implemented) ## JSX - JSX should only be responsible for UI rendering and some rendering logic. Higher order logic should be performed outside of the return statement, or better yet, outside of the component (if possible). - Eliminate as much as logic as possible from JSX - Eliminate as much style as possible from JSX. - Anonomous callback functions inside of props create a new function every time, and will eventually bog down a computer's memory. Unless completely necessary, save a function to a constant inside the component (ex. handleClick) and pass it to props as a callback. - Complex nested ternary patterns should replaced with functions that use if/else blocks for control flow. - Complex ternary patterns are a good sign that this piece of the UI can be its own component ## Transactions and Polls - Polls are used to continually fetch data from an API after a TX - Tests are used to test whether or not that data has updated - Once the test passed, we have functions that refresh the React app state. - Polls must test the item that React reads for UI updates. For example, if React gets a bank balance from the graph, then a TX poll should be aimed at the part of the graph data that is expecting the value to update. - Polls must pass all of the necessary arguments to cachePoll() as well so that the poll can be automatically rebuilt if the user closes, then opens their window - Cache poll should be tested for every new transaction ## Convention Wishlist - Maintenance Epic: Create a better file structure system. It's looking a little bloated - Maintenance Epic: Create a way to refactor those massive TX functions out of the components and into a context, while also creating a single way to log every error if somthing goes wrong. - Maintenance Epic: Research best implementation of error boundaries and build a pattern for the app. Apply throughout the app. - Maintenance Epic: Optimise a sane, robust, and efficient way of resolving and updating proposal data from the graph. Create a standard that is implemented from when a TX is fired, to final render. Data should have a consistent shape. - Maintenance task: Discuss our bulkiest components and add a refactor bounty for each item to the bounty bucket. Price by difficulty. - Maintenance task: Discuss a pattern around naming. - Maintenance taks: Create a JS style guide repo where people (especially new devs) can leave feedback on patterns and current rules. - Maintenance Epic: Create a common pattern for loading UI and state. Implement across the app.