Giter VIP home page Giter VIP logo

singel's Introduction

singel

Generated with nod NPM version Build Status Coverage Status

Single Element Pattern (Singel) is a set of rules/best practices to create consistent, reliable and maintainable components in React and other component-based libraries. This is based on the idea that the building blocks of an application should resemble as most as possible native HTML elements. Read full article

This repo is a CLI tool for checking whether React components conform to the Singel pattern.


Example



Installation

$ npm i -g singel

Usage

$ singel path/to/**/Component.js --ignore "path/to/**/ignored/Component.js"

Projects applying Singel

Feel free to send a PR adding your open source project

Rules

Render only one element

// bad - 2 elements
const Element = props => (
  <div {...props}>
    <span />
  </div>
);

// good
const Element = props => (
  <div {...props} />
);

// good - if Element is good
const Element2 = props => (
  <Element {...props} />
);

Never break the app

// good
const Element = props => (
  <div {...props} />
);

// bad - will break if getId wasn't provided
const Element = ({ getId, ...props }) => (
  <div id={getId()} {...props} />
);

// bad - will break if foo wasn't provided
const Element = ({ foo, ...props }) => (
  <div id={foo.bar} {...props} />
);

Render all HTML attributes passed as props

// good
const Element = props => (
  <div {...props} />
);

// bad - not rendering id
const Element = ({ id, ...props }) => (
  <div {...props} />
);

// good
const Element = ({ id, ...props }) => (
  <div id={id} {...props} />
);

Always merge the styles passed as props

// good
const Element = props => (
  <div {...props} />
);

// bad - not rendering className
const Element = ({ className, ...props }) => (
  <div {...props} />
);

// bad - not rendering style
const Element = ({ style, ...props }) => (
  <div {...props} />
);

// bad - replacing className instead of appending
const Element = props => (
  <div className="foo" {...props} />
);

// bad - replacing style instead of merging
const Element = props => (
  <div style={{ padding: 0 }} {...props} />
);

// good
const Element = ({ className, ...props }) => (
  <div className={`foo ${className}`} {...props} />
);

// good
const Element = ({ style, ...props }) => (
  <div style={{ padding: 0, ...style }} {...props} />
);

Add all the event handlers passed as props

// good
const Element = props => (
  <div {...props} />
);

// bad - not passing onClick
const Element = ({ onClick, ...props }) => (
  <div {...props} />
);

// bad - replacing onClick prop
const Element = props => (
  <div {...props} onClick={myFunction} />
);

// good
const Element = ({ onClick, ...props }) => (
  <div onClick={onClick} {...props} />
);

// good - it's ok to replace internal event handlers
const Element = props => (
  <div onClick={myFunction} {...props} />
);

// good - calling internal and prop
const callAll = (...fns) => (...args) => 
  fns.forEach(fn => fn && fn(...args));

const Element = ({ onClick, ...props }) => (
  <div onClick={callAll(myFunction, onClick)} {...props} />
);

FAQ

How to handle nested elements?

Say you have a Button element and you want to display a Tooltip when it's hovered. The first rule you'll want to break is rendering only one element. To handle that you have some options:

  • Use CSS pseudo-elements (such as :after and :before);
  • Create a non-singel element, which is fine;
  • Nest styles instead of components.

Here's an example of how you can accomplish tha latter one:

/* could also be CSS-in-JS */
.button {
  position: relative;
  /* more button css */
}

.button:hover .tooltip {
  display: block;
}

.button .tooltip {
  display: none;
  position: absolute;
  /* more tooltip css */
}
const Button = ({ className, ...props }) => (
  <button className={`button ${className}`} {...props} />
);

Button.Tooltip = ({ className, ...props }) => (
  <div className={`tooltip ${className}`} {...props} />
);

Usage:

<Button className="my-specific-button">
  <Button.Tooltip className="my-specific-tooltip">
    😁
  </Button.Tooltip>
  Hover me
</Button>

Both Button and Button.Tooltip are single elements. You have all the benefits you would have by nesting them, but now with complete control over Button.Tooltip from outside.

License

MIT © Diego Haz

singel's People

Contributors

dependabot[bot] avatar diegohaz avatar jyash97 avatar tcodes0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

singel's Issues

Doesn't work as dependency

Hi. First times testing with this tool and seems that I hit a bug. Singel package doesn't work as dependency. I tried it as a both, dev and prod dependency. But still package works if installed globally.

I'm using custom Webpack configuration for React. I tried to Google error message and got reference to Enzyme package.

Dependencies

"dependencies": {
    "express": "^4.16.3",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-router": "^4.2.0",
    "react-router-dom": "^4.2.2",
    "socket.io": "^2.1.1",
    "socket.io-client": "^2.1.1"
  },
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "css-loader": "^0.28.11",
    "eslint": "^4.19.1",
    "eslint-config-prettier": "^2.9.0",
    "eslint-config-standard": "^11.0.0",
    "eslint-plugin-import": "^2.12.0",
    "eslint-plugin-node": "^6.0.1",
    "eslint-plugin-prettier": "^2.6.0",
    "eslint-plugin-promise": "^3.7.0",
    "eslint-plugin-react": "^7.8.2",
    "eslint-plugin-standard": "^3.1.0",
    "html-webpack-plugin": "^3.2.0",
    "husky": "^0.14.3",
    "node-sass": "^4.9.0",
    "prettier": "^1.12.1",
    "sass-loader": "^7.0.1",
    "singel": "^0.5.3",
    "style-loader": "^0.21.0",
    "webpack": "^4.8.3",
    "webpack-cli": "^2.1.3",
    "webpack-serve": "^1.0.2"
  }

Component

Card.js

import React from 'react';
import './Card.scss';

const Card = ({ className, ...props }) => (
  <div className={`card ${className}`} {...props} />
);

export default Card;

When running as dependency

package.json

"scripts": {
    "singel": "singel src/components/*/index.js"
  }

Console

✖ Card src/components/Card/index.js
  Don't break: ReactWrapper::simulate() event 'pointerDown' does not exist
  Call event handlers passed as props: onPointerDown
  Don't break: ReactWrapper::simulate() event 'pointerMove' does not exist
  Call event handlers passed as props: onPointerMove
  Don't break: ReactWrapper::simulate() event 'pointerUp' does not exist
  Call event handlers passed as props: onPointerUp
  Don't break: ReactWrapper::simulate() event 'pointerCancel' does not exist
  Call event handlers passed as props: onPointerCancel
  Don't break: ReactWrapper::simulate() event 'gotPointerCapture' does not exist
  Call event handlers passed as props: onGotPointerCapture
  ... and 10 more errors.

When running globally

CLI

singel src/components/*/index.js

Console

✔ Card src/components/Card/index.js

Doesn't work as dev dependency

Hi, I'm trying to add this into a build as a development dependency so as not to depend on things outside the project. I get an error after doing this:

  • npm i -D singel
  • package.json script: "lint": "eslint --ignore-path .gitignore . && singel src/singles/**/*.js",
  • npm run lint

My /src/singles/Element.js component:

import React from "react";
const Element = props => <div {...props} />;
export default Element;

Output:

> npm run lint

> [email protected] lint X:\app
> eslint --ignore-path .gitignore . && singel src/singles/**/*.js


X:\app\node_modules\babel-core\lib\transformation\file\index.js:558
      throw err;
      ^

SyntaxError: X:/hw-bb/ddhughes/recipe-app/src/singles/Element.js: Unexpected token (3:25)
  1 | import React from "react";
  2 |
> 3 | const Element = props => <div {...props} />;
    |                          ^
  4 |
  5 | export default Element;
  6 |
    at Parser.pp$5.raise (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:4454:13)
    at Parser.pp.unexpected (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:1761:8)
    at Parser.pp$3.parseExprAtom (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3750:12)
    at Parser.pp$3.parseExprSubscripts (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3494:19)
    at Parser.pp$3.parseMaybeUnary (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3474:19)
    at Parser.pp$3.parseExprOps (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3404:19)
    at Parser.pp$3.parseMaybeConditional (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3381:19)
    at Parser.pp$3.parseMaybeAssign (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:3344:19)
    at Parser.pp$3.parseFunctionBody (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:4226:22)
    at Parser.pp$3.parseArrowExpression (X:\hw-bb\ddhughes\recipe-app\node_modules\babylon\lib\index.js:4190:8)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] lint: `eslint --ignore-path .gitignore . && singel src/singles/**/*.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\dh\AppData\Roaming\npm-cache\_logs\2018-07-03T10_37_38_718Z-debug.log

Cannot find module 'prop-types'

Hi,

When I try and run the tool with the most basic form, I'm getting the following error:
Error: Cannot find module 'prop-types'
Also the --ignore doesn't seem to be taken into account.
What am I missing?

Thanks :)

Getting errors for simple example using create-react-app

I tried singel by using create-react-app and running singel src/App.js which gives me

~/test-singel  singel src/App.js
/Users/tschloetel/test-singel/src/logo.svg:4
React.createElement(
^

ReferenceError: React is not defined
    at Object.<anonymous> (/Users/tschloetel/test-singel/src/logo.svg:1:1)
    at Module._compile (module.js:652:30)
    at loader (/usr/local/lib/node_modules/singel/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .js] (/usr/local/lib/node_modules/singel/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/Users/tschloetel/test-singel/src/App.js:2:1)

Then I created Test.js with the example from the docs:

// bad - 2 elements
const Element = props => (
  <div {...props}>
    <span />
  </div>
);

// good
const Element2 = props => (
  <div {...props} />
);

// good - if Element is good
const Element3 = props => (
  <Element {...props} />
);

I ran singel Test.js and get

usr/local/lib/node_modules/singel/dist/Logger.js:47
    const elementName = this.element.displayName || this.element.name;
                                     ^

TypeError: Cannot read property 'displayName' of undefined
    at Logger.start (/usr/local/lib/node_modules/singel/dist/Logger.js:47:38)
    at realPaths.forEach (/usr/local/lib/node_modules/singel/dist/cli.js:68:12)
    at Array.forEach (<anonymous>)
    at run (/usr/local/lib/node_modules/singel/dist/cli.js:57:13)
    at Object.<anonymous> (/usr/local/lib/node_modules/singel/dist/cli.js:91:1)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)

About separate of concerns to make the components easy to reason about

Hi, great job!

I've loved this library and the Singel's concepts, that's really awesome. But there is a concept that I think should be dangerous, like the obligation to pass the style and classNames props up to the parent component.

I think this concept so much rigorous to make your application not scalable. I think it make sense in some cases, but not at all. Sometimes I need to encapsulate the styles of a component and control it by a prop as the article is against.

When you are thinking about the responsibility of the components. When you would like to create the styled components with the characteristics of your application, and you want to give this responsibility just to the styled components, so the styles will be just into the styled component.

The benefits

  • It keeps you application clear, because you wont use styles in whole application.
  • When you need to change the style or a behaviour, you will just change a single component.
  • It fits as a glove about separating presentational and container components

Resume

In my opinion, it would be a bad approach.

// good
const Element = ({ className, ...props }) => (
  <div className={`foo ${className}`} {...props} />
);

// good
const Element = ({ style, ...props }) => (
  <div style={{ padding: 0, ...style }} {...props} />
);

Therefore you are allowing this:

const WrapperContainerComponent = ({ isChecked }) => (
  <Element style={{ padding: 0, backgroundColor: isChecked ? "blue" : "white" }}  />
);

Instead of this:

// Wrapper
const WrapperContainerComponent = ({ isChecked }) => (
  <Element isChecked={isChecked}  />
);
//Styled component
const Element = ({ isChecked }) => (
  <div style={getStyles(isChecked)}  />
);

const getStyles = (isChecked) => ({
  padding: 0,
  backgroundColor: isChecked ? "blue" : "white"
})

Make cli output more helpful to user

It currently throws on the first error, it would be better to display all errors in a report, or something. console.group and chalk come to mind 🙂🖌🌈.

In case it passes everything print a message saying that (currently prints nothing at all)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.