├── .eslintrc.json
├── .github
└── workflows
│ └── actions.yml
├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── renovate.json
├── src
├── .prettierrc.js
├── App.css
├── App.test.tsx
├── App.tsx
├── hooks
│ └── index.tsx
├── index.css
├── index.tsx
├── react-app-env.d.ts
├── serviceWorker.ts
└── setupTests.ts
├── tsconfig-lib.json
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:react/recommended",
9 | "plugin:@typescript-eslint/eslint-recommended"
10 | ],
11 | "globals": {
12 | "Atomics": "readonly",
13 | "SharedArrayBuffer": "readonly"
14 | },
15 | "parser": "@typescript-eslint/parser",
16 | "parserOptions": {
17 | "ecmaFeatures": {
18 | "jsx": true
19 | },
20 | "ecmaVersion": 2018,
21 | "sourceType": "module"
22 | },
23 | "plugins": [
24 | "react",
25 | "@typescript-eslint"
26 | ],
27 | "rules": {
28 | },
29 | "settings": {
30 | "react": {
31 | "version": "detect"
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/.github/workflows/actions.yml:
--------------------------------------------------------------------------------
1 | name: deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 |
12 | strategy:
13 | matrix:
14 | node-version: [12.x]
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 |
19 | - name: Build Example app - ${{ matrix.node-version }}
20 | uses: actions/setup-node@v2
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 |
24 | - name: Get yarn cache directory path
25 | id: yarn-cache-dir-path
26 | run: echo "::set-output name=dir::$(yarn cache dir)"
27 |
28 | - uses: actions/cache@v3
29 | id: yarn-cache
30 | with:
31 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
32 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
33 | restore-keys: |
34 | ${{ runner.os }}-yarn-
35 |
36 | - name: Install Dependencies
37 | run: yarn install --frozen-lockfile
38 |
39 | - name: Build static example
40 | run: yarn build
41 |
42 | - name: Build Hook
43 | run: yarn build:hook
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | node_modules
3 | lib
4 | scripts/flow/*/.flowconfig
5 | *~
6 | *.pyc
7 | .grunt
8 | _SpecRunner.html
9 | __benchmarks__
10 | build/
11 | remote-repo/
12 | coverage/
13 | .module-cache
14 | fixtures/dom/public/react-dom.js
15 | fixtures/dom/public/react.js
16 | test/the-files-to-test.generated.js
17 | *.log*
18 | chrome-user-data
19 | *.sublime-project
20 | *.sublime-workspace
21 | .idea
22 | *.iml
23 | .vscode
24 | *.swp
25 | *.swo
26 |
27 | packages/react-devtools-core/dist
28 | packages/react-devtools-extensions/chrome/build
29 | packages/react-devtools-extensions/chrome/*.crx
30 | packages/react-devtools-extensions/chrome/*.pem
31 | packages/react-devtools-extensions/firefox/build
32 | packages/react-devtools-extensions/firefox/*.xpi
33 | packages/react-devtools-extensions/firefox/*.pem
34 | packages/react-devtools-extensions/shared/build
35 | packages/react-devtools-inline/dist
36 | packages/react-devtools-shell/dist
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # use-cep-hook :earth_americas:
2 |
3 | [](https://www.npmjs.com/package/use-cep-hook)
4 |
5 |
6 | This is a simple React Hooks that let you search for a brazillian postal-code.
7 |
8 | # Installation
9 |
10 | Just run a ```yarn add use-cep-hook``` or ```npm i --save use-cep-hook```
11 |
12 | # How to use
13 |
14 | Simply import `use-cep-hook` on your component, then call this on that way:
15 |
16 | ```jsx
17 | import useCep from "use-cep-hook";
18 |
19 | const Foo = () => {
20 | const [postalCode, setPostalCode] = useState("");
21 |
22 | const [loading, cep, error] = useCep(postalCode);
23 |
24 | return (
25 |
26 |
27 | setPostalCode(e.target.value)} value={postalCode} />
28 |
29 | );
30 | };
31 | ```
32 |
33 | Every time that a valid CEP is passed to this hooks, this will cause a side effect that will search for the updated value.
34 |
35 | You can check the full implementation [here](https://github.com/MateusAndrade/use-cep-hook/blob/master/src/hooks/index.tsx)!
36 |
37 | ## TODO:
38 |
39 | - [x] Port to TS
40 | - [ ] Apply some cool CSS to `example`
41 | - [ ] Write TESTS!
42 | - [ ] Deploy example to GH pages!
43 | - [ ] Configure Github Actions
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-cep-hook",
3 | "version": "1.1.0",
4 | "description": "A simple React Hook to fetch info of a CEP!",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/MateusAndrade/use-cep-hook.git"
10 | },
11 | "author": "MateusAndrade",
12 | "license": "ISC",
13 | "bugs": {
14 | "url": "https://github.com/MateusAndrade/use-cep-hook/issues"
15 | },
16 | "homepage": "https://MateusAndrade.github.io/use-cep-hook",
17 | "keywords": [
18 | "cep",
19 | "postalcode",
20 | "hook",
21 | "react"
22 | ],
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject",
28 | "build:hook": "tsc --project tsconfig-lib.json",
29 | "deploy-example": "gh-pages -d build",
30 | "lint": "eslint . --ext .js"
31 | },
32 | "dependencies": {
33 | "cep-promise": "^4.0.0"
34 | },
35 | "devDependencies": {
36 | "@commitlint/cli": "17.6.1",
37 | "@commitlint/config-conventional": "17.6.1",
38 | "@testing-library/jest-dom": "5.16.5",
39 | "@testing-library/react": "14.0.0",
40 | "@testing-library/user-event": "13.5.0",
41 | "@types/jest": "27.4.1",
42 | "@types/node": "16.18.21",
43 | "@types/react": "18.0.31",
44 | "@types/react-dom": "18.0.11",
45 | "eslint": "8.36.0",
46 | "eslint-plugin-react": "7.29.4",
47 | "gh-pages": "3.2.3",
48 | "husky": "7.0.4",
49 | "lint-staged": "12.3.7",
50 | "prettier": "2.8.7",
51 | "react": "18.2.0",
52 | "react-dom": "18.2.0",
53 | "react-scripts": "5.0.1",
54 | "typescript": "4.9.5"
55 | },
56 | "peerDependecies": {
57 | "react": "^16.8.0"
58 | },
59 | "lint-staged": {
60 | "*.{ts,tsx}": "eslint"
61 | },
62 | "hooks": {
63 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
64 | "pre-commit": "yarn lint-staged"
65 | },
66 | "browserslist": {
67 | "production": [
68 | ">0.2%",
69 | "not dead",
70 | "not op_mini all"
71 | ],
72 | "development": [
73 | "last 1 chrome version",
74 | "last 1 firefox version",
75 | "last 1 safari version"
76 | ]
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateusAndrade/use-cep-hook/738ce74c39d2ab036e841d43d044d11903a84489/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
19 |
20 |
29 | React App
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateusAndrade/use-cep-hook/738ce74c39d2ab036e841d43d044d11903a84489/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateusAndrade/use-cep-hook/738ce74c39d2ab036e841d43d044d11903a84489/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "packageRules": [
6 | {
7 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
8 | "automerge": true
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | };
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateusAndrade/use-cep-hook/738ce74c39d2ab036e841d43d044d11903a84489/src/App.css
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import './App.css';
4 |
5 | import useCepHook from "./hooks"
6 |
7 | const App = () => {
8 | const [searchValue, setSearchValue] = useState("");
9 |
10 | const [loading, cep, error] = useCepHook(searchValue);
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 | setSearchValue(e.target.value)} value={searchValue}/>
20 |
21 |
22 |
23 |
24 |
25 | - Loading: {String(loading)}
26 | - CEP: {JSON.stringify(cep)}
27 | - Error: {JSON.stringify(error)}
}
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/src/hooks/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useMemo, useCallback } from "react";
2 |
3 | import fetchCep from "cep-promise";
4 |
5 | export type Cep = {
6 | cep: string;
7 | state: string;
8 | city: string;
9 | street: string;
10 | neighborhood: string;
11 | };
12 |
13 | export type ErrorCep = { hasError: boolean, message: string };
14 |
15 | export type HookReturn = [boolean, Cep, ErrorCep];
16 |
17 | const INITIAL_CEP: Cep = {
18 | cep: "",
19 | state: "",
20 | city: "",
21 | street: "",
22 | neighborhood: ""
23 | };
24 |
25 | const useCep = (search: string | number): HookReturn => {
26 | const cleanCep: string = useMemo(() => String(search).replace(/\D+/g, ""), [
27 | search
28 | ]);
29 |
30 | const [loading, setLoading] = useState(false);
31 |
32 | const [cep, setCep] = useState(INITIAL_CEP);
33 |
34 | const [error, setError] = useState({ hasError: false, message: "" });
35 |
36 | const searchCep = useCallback(async () => {
37 | setLoading(true);
38 | setError({ hasError: false, message: "" });
39 |
40 | try {
41 | const response = await fetchCep(cleanCep);
42 |
43 | setCep(response);
44 |
45 | setLoading(false);
46 | } catch (error) {
47 | const message: string = error instanceof Object ? String(error) : error;
48 |
49 | setCep(INITIAL_CEP);
50 | setError({ hasError: true, message });
51 | setLoading(false);
52 | }
53 | }, [cleanCep]);
54 |
55 | useEffect(() => {
56 | if (cleanCep.length === 8) {
57 | searchCep();
58 | }
59 | }, [cleanCep, searchCep]);
60 |
61 | return [loading, cep, error];
62 | };
63 |
64 | export default useCep;
65 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want your app to work offline and load faster, you can change
15 | // unregister() to register() below. Note this comes with some pitfalls.
16 | // Learn more about service workers: https://bit.ly/CRA-PWA
17 | serviceWorker.unregister();
18 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config: any) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl: string, config: any) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl: string, config: any) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/tsconfig-lib.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "compilerOptions": {
4 | "target": "es5",
5 | "module": "commonjs",
6 | "moduleResolution": "node",
7 | "jsx": "react",
8 | "declaration": true,
9 | "pretty": true,
10 | "rootDir": "src/hooks",
11 | "sourceMap": false,
12 | "strict": true,
13 | "esModuleInterop": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noImplicitReturns": true,
17 | "noImplicitAny": false,
18 | "noFallthroughCasesInSwitch": true,
19 | "outDir": "lib",
20 | "lib": [
21 | "es2018",
22 | "dom"
23 | ],
24 | "importHelpers": true
25 | },
26 | "exclude": [
27 | "node_modules",
28 | "src/index.tsx",
29 | "src/App.tsx",
30 | "src/App.test.tsx",
31 | "src/serviceWorker.ts",
32 | "src/setupTests.ts"
33 | ]
34 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------