├── .github
├── FUNDING.yml
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── package.json
├── src
└── index.tsx
├── test
└── index.test.tsx
├── tsconfig.json
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: sergiodxa
2 | github: sergiodxa
3 | custom: https://paypal.me/sergiodxa
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | time: "10:00"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | test:
7 | name: Test
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Use Node.js
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: 12
16 | - name: Install
17 | run: yarn install
18 | - name: Test
19 | run: yarn test
20 | env:
21 | CI: true
22 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v1
12 | - uses: actions/setup-node@v1
13 | with:
14 | node-version: 12
15 | registry-url: https://registry.npmjs.org/
16 | - run: yarn install
17 | - run: npm publish --access public
18 | env:
19 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at hello@sergiodxa.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Sergio Xalambrí
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # useLog  
2 |
3 | > Log a state or prop every time it changes
4 |
5 | ## Usage
6 |
7 | Install it:
8 |
9 | ```sh
10 | $ yarn add use-log
11 | ```
12 |
13 | Import it:
14 |
15 | ```ts
16 | import useLog from 'use-log';
17 | ```
18 |
19 | Use it:
20 |
21 | ```ts
22 | function MyComponent() {
23 | const [value, setValue]= React.useState("")
24 | useLog(`The value is ${value}`);
25 | return setValue(event.target.value)}>
26 | }
27 | ```
28 |
29 | Now you will get a log with `The value is ${value}` everytime the message change, this will happen everytime the value change.
30 |
31 | ### Log objects or arrays
32 |
33 | When using it with an object or array as value to log you may want to memoize it to avoid the log running on every render:
34 |
35 | ```ts
36 | function MyComponent() {
37 | const [value, setValue]= React.useState("")
38 | useLog(React.useMemo(() => ({ value }), [value]));
39 | return setValue(event.target.value)}>
40 | }
41 | ```
42 |
43 | ### Configuration
44 |
45 | useLog receives an optional configuration object as second argument with the following interface:
46 |
47 | ```js
48 | interface Config {
49 | level?: 'log' | 'info' | 'warn' | 'error' | 'debug' | 'dir' | 'table';
50 | shouldLogInProduction?: boolean;
51 | }
52 | ```
53 |
54 | ### Changing the log level
55 |
56 | You can change the log level this way:
57 |
58 | ```ts
59 | function MyComponent() {
60 | const [value, setValue]= React.useState("")
61 | useLog(`The value is ${value}`, { level: "debug" });
62 | return setValue(event.target.value)}>
63 | }
64 | ```
65 |
66 | This will basically change the `console` method useLog is calling.
67 |
68 | ### Production-safe
69 |
70 | You can keep the hook in your code and the code will do nothing in production by default, if you want to enable it in production environments you can set `shouldLogInProduction` to `true`.
71 |
72 | ```ts
73 | function MyComponent() {
74 | const [value, setValue]= React.useState("")
75 | useLog(`The value is ${value}`, { shouldLogInProduction: true });
76 | return setValue(event.target.value)}>
77 | }
78 | ```
79 |
80 | This way the log will continue working in production.
81 |
82 | ## Author
83 |
84 | - [Sergio Xalambrí](https://sergiodxa.com) - [Able](https://able.co)
85 |
86 | ## License
87 |
88 | The MIT License.
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-log",
3 | "description": "Log a state or prop every time it changes",
4 | "version": "1.1.0",
5 | "author": "Sergio Xalambrí",
6 | "license": "MIT",
7 | "main": "dist/index.js",
8 | "module": "dist/use-log.esm.js",
9 | "typings": "dist/index.d.ts",
10 | "files": [
11 | "dist",
12 | "src"
13 | ],
14 | "engines": {
15 | "node": ">=10"
16 | },
17 | "scripts": {
18 | "start": "tsdx watch",
19 | "build": "tsdx build",
20 | "test": "tsdx test --passWithNoTests",
21 | "lint": "tsdx lint",
22 | "prepare": "tsdx build"
23 | },
24 | "peerDependencies": {
25 | "react": ">=16"
26 | },
27 | "husky": {
28 | "hooks": {
29 | "pre-commit": "tsdx lint"
30 | }
31 | },
32 | "prettier": {
33 | "printWidth": 80,
34 | "semi": true,
35 | "singleQuote": true,
36 | "trailingComma": "es5"
37 | },
38 | "devDependencies": {
39 | "@testing-library/react": "^12.0.0",
40 | "@types/react": "^17.0.0",
41 | "@types/react-dom": "^17.0.0",
42 | "husky": "^7.0.0",
43 | "react": "^17.0.0",
44 | "react-dom": "^17.0.0",
45 | "tsdx": "^0.14.0",
46 | "tslib": "^2.0.0",
47 | "typescript": "^4.0.2"
48 | },
49 | "dependencies": {
50 | "use-consistent-value": "^1.0.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import useConsistentValue from 'use-consistent-value';
3 |
4 | export type Level =
5 | | 'log'
6 | | 'info'
7 | | 'warn'
8 | | 'error'
9 | | 'debug'
10 | | 'dir'
11 | | 'table';
12 |
13 | export interface UseLogConfig {
14 | level?: Level;
15 | shouldLogInProduction?: boolean;
16 | }
17 |
18 | export function useLog(
19 | message: Message,
20 | config?: UseLogConfig
21 | ): void;
22 | export function useLog(
23 | message: Message,
24 | { level = 'log', shouldLogInProduction = false }: UseLogConfig = {}
25 | ): void {
26 | const value = useConsistentValue(message);
27 | useEffect(() => {
28 | if (!shouldLogInProduction && process.env.NODE_ENV === 'production') return;
29 | switch (level) {
30 | case 'error':
31 | return console.error(value);
32 | case 'warn':
33 | return console.warn(value);
34 | case 'info':
35 | return console.info(value);
36 | case 'debug':
37 | return console.debug(value);
38 | case 'dir':
39 | return console.dir(value);
40 | case 'table':
41 | return console.table(value);
42 | case 'log':
43 | return console.log(value);
44 | default:
45 | throw new Error('Invalid log level');
46 | }
47 | }, [value, level, shouldLogInProduction]);
48 | }
49 |
50 | export default useLog;
51 |
--------------------------------------------------------------------------------
/test/index.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from '@testing-library/react';
3 | import useLog, { Level } from '../src';
4 |
5 | interface TesterProps {
6 | message: string;
7 | level: Level;
8 | }
9 |
10 | function Tester({ message, level }: TesterProps): null {
11 | useLog(message, { level });
12 | return null;
13 | }
14 |
15 | describe(useLog, () => {
16 | beforeEach(() => {
17 | jest.resetAllMocks();
18 | });
19 |
20 | describe('Log message as level', () => {
21 | test('log', () => {
22 | const spy = jest.spyOn(console, 'log').mockImplementation();
23 | render();
24 | expect(spy).toHaveBeenCalledWith('testing');
25 | });
26 |
27 | test('info', () => {
28 | const spy = jest.spyOn(console, 'info').mockImplementation();
29 | render();
30 | expect(spy).toHaveBeenCalledWith('testing');
31 | });
32 |
33 | test('warn', () => {
34 | const spy = jest.spyOn(console, 'warn').mockImplementation();
35 | render();
36 | expect(spy).toHaveBeenCalledWith('testing');
37 | });
38 |
39 | test('error', () => {
40 | const spy = jest.spyOn(console, 'error').mockImplementation();
41 | render();
42 | expect(spy).toHaveBeenCalledWith('testing');
43 | });
44 |
45 | test('debug', () => {
46 | const spy = jest.spyOn(console, 'debug').mockImplementation();
47 | render();
48 | expect(spy).toHaveBeenCalledWith('testing');
49 | });
50 |
51 | test('dir', () => {
52 | const spy = jest.spyOn(console, 'dir').mockImplementation();
53 | render();
54 | expect(spy).toHaveBeenCalledWith('testing');
55 | });
56 |
57 | test('table', () => {
58 | const spy = jest.spyOn(console, 'table').mockImplementation();
59 | render();
60 | expect(spy).toHaveBeenCalledWith('testing');
61 | });
62 | });
63 |
64 | test('Log after message change', () => {
65 | const spy = jest.spyOn(console, 'log').mockImplementation();
66 | const { rerender } = render();
67 | expect(spy).toHaveBeenCalledWith('testing');
68 | rerender();
69 | expect(spy).toHaveBeenCalledTimes(2);
70 | expect(spy).toHaveBeenLastCalledWith('testing-2');
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types"],
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "lib": ["dom", "esnext"],
6 | "importHelpers": true,
7 | "declaration": true,
8 | "sourceMap": true,
9 | "rootDir": "./src",
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "noUnusedParameters": true,
13 | "noImplicitReturns": true,
14 | "noFallthroughCasesInSwitch": true,
15 | "moduleResolution": "node",
16 | "baseUrl": "./",
17 | "paths": {
18 | "*": ["src/*", "node_modules/*"]
19 | },
20 | "jsx": "react",
21 | "esModuleInterop": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------