Cleaning things up

JS Clean Architecture

Context is important

Reading code

>

Writing code

Architecture?

micro-frontends!

How it feels to learn JavaScript in 2016

Clean Code

(single module level)


                    import React from 'react';
                    import { makeStyles } from '@material-ui/core/styles';

                    const useStyles = makeStyles({export const useStyles = makeStyles({
                        root: {
                            backgroundColor: 'red',
                            color: props => props.color,
                        },
                    });

                    export default function MyComponent(props) {
                        const classes = useStyles(props);
                        return <div className={classes.root} />;
                    }
                    

                    import React from 'react';
                    import { makeStyles } from '@material-ui/core/styles';
                    
                    export default function MyComponent(props) {
                        const classes = useStyles(props);
                        return <div className={classes.root} />;
                    }
                    
                    const useStyles = makeStyles({
                        root: {
                            backgroundColor: 'red',
                            color: props => props.color,
                        },
                    });
                    
source: material-ui.com
Name? MyComponent.jsx

Java


                        public class Test {
                          public static final int A = 77;

                          protected int b;
                          private String name;
                        
                          Test() {
                            this.a = 0;
                          }
                        
                          public void foo() {
                            help();
                          }
                        
                          private void help() {
                            // ...
                          }
                        }

                        class NotPublic {
                          // similar to above
                        }
                    

johnpapa/angular-styleguide

Place bindable members at the top (...) and not spread through the controller code

                    /* recommended */
                    function SessionsController() {
                        var vm = this;

                        vm.gotoSession = gotoSession;
                        vm.refresh = refresh;
                        vm.search = search;
                        vm.sessions = [];
                        vm.title = 'Sessions';

                        ////////////

                        function gotoSession() {
                            /* */
                        }

                        function refresh() {
                            /* */
                        }

                        function search() {
                            /* */
                        }
                    }
                    

                        export interface Dataservice {
                            getAvengers(): Avenger[];
                        }

                        angular
                            .module('app.core')
                            .factory('dataservice', dataservice);
                        
                        dataservice.$inject = ['$http'];
                        function dataservice($http: IHttpService): Dataservice {
                            return {
                                getAvengers
                            };
                        
                            function getAvengers(): Avenger[] {
                                return $http.get('/api/maa')
                                    .then(getAvengersComplete)
                                    .catch(getAvengersFailed);
                        
                                function getAvengersComplete(response) { /* ... */ }
                        
                                function getAvengersFailed(error) { /* ... */ }
                            }
                        }
                    

Clean Architecture

(organizing modules)

Problems?

  • Harder to work on a feature (e.g. user/project)
    • Usually a feature-related issue
    • Not e.g. fixing all the services
    • Looking for a reason in multiple directories
    • Writing in multiple directories
  • Many (7+) files per folder
    • Especially in components

Let's introduce state!

Redux

  • Doesn't organize the app
  • Different purpose - working/sharing the data
  • A way of building the app

react-file-structure.surge.sh

johnpapa/angular-styleguide

LIFT Principle

  1. Locate easily
  2. Identify the file purpose
  3. Flat folder structure
  4. T-DRY (Try to Stick to DRY)

Folders-by-Feature Structure

  • Folders named for the feature they represent
  • Helps following LIFT

How to divide files in folder?

  • Presentational and Container Components
    • I’ve seen it enforced (...) with almost dogmatic fervor far too many times
    • Table, TableView - come on
    • Sometimes handy to have a small state in presentational-like component
  • (...) it let me separate complex stateful logic from other aspects of the component. Hooks let me do the same (...) without an arbitrary division

Hooks?

  • Good for cross-cutting business logic, e.g. useAssignedTags
  • Useful rather for technical aspects - routing, useMountSubscription
  • Not a silver bullet

Building blocks

  • Angular: service, module, component, pipe, ...
  • React:

My recommendations

  • Define, agree & use them. All over the codebase!
  • Defer Controller Logic to Services
  • MVC!
    • Many MVC approaches
    • Model - data from API & functions processing it
    • View - JSX; worth dividing into presentational & container components
    • Controller - logic used in View

                        import React, { useState } from 'react';
                        import { useMountSubscription } from '../common';

                        import { getProject, Project as Model } from './project.api';
                        import { clickProject } from './project.service';

                        export interface ProjectProps {
                            id: number;
                        }

                        export function Project({ id }: ProjectProps) {
                            const [project, setProject] = useState<Model>();
                            useMountSubscription(() => getProject(id).then(setProject));
                            if (!project) {
                                return null;
                            }
                            return (
                                <ul onClick={() => clickProject(project)}>
                                    <li>{project.name}</li>
                                    <li>{project.type}</li>
                                </ul>
                            );
                        }

                    

Achievements

  • Super nice tests - just for clickProject
    • Testing context, history, others with React Testing Library = mess
  • Easy to change framework/solution
    • React = just View from MVC. As it should be
    • A JavaScript library for building user interfaces
  • Easy to maintain after the first shock

Clean Architecture

(packages)

What when own design system?

  • Bare minimum - separate folder, e.g. layout
  • First project using design system - monorepo
  • Next - internal npm and separate packages
  • Big company - adapters approach
    1. CSS package
    2. Web Components package
    3. React (and other) adapters for Web Components
    4. Application

Thanks!