diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..6778380 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: aromalanil +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: ['https://paypal.me/aromalanil','https://www.buymeacoffee.com/aromalanil'] \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dbded66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: "\U0001F41E Found a bug? Report it here." +title: '' +labels: bug +assignees: '' + +--- + +**Short Title** +Give a short title for the issue + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See an error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..430b399 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +tsconfig.json +src +.github \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f5f7589 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,15 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": true, + "parser": "flow", + "semi": true, + "useTabs": false, + "quoteProps": "as-needed", + "jsxSingleQuote": true, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ef108b --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# 🦏 React Rhino + +React Rhino is a simple yet powerful state management library for [React](https://http://reactjs.org/) + +## Installation + +```bash +# If you use npm: +npm install react-rhino + +# Or if you use Yarn: +yarn add react-rhino +``` + +## Usage + +### Step 1 +1. Create a `states.js` file in your `src` folder +2. Import `createRhinoState` from `react-rhino` + +```js +import createRhinoState from 'react-rhino' +``` + +3. Pass an object to the `createRhinoState` function where the keys uniquely identify the state and values will be the initial value of the corresponding state. + +```js +const { RhinoProvider, useRhinoState } = createRhinoState({ + name: "John Doe", + isLoggedIn: true +}) +``` +4. `export` the `RhinoProvider` and `useRhinoState` from the file + +```js +export { RhinoProvider, useRhinoState } +``` + +### Step 2 +1. Import the `RhinoProvider` from the file and wrap the `App` component with it. + +```js +import { RhinoProvider } from './states.js` +import App from "./App"; + +const rootElement = document.getElementById("root"); +ReactDOM.render( + + + , + rootElement +); + +``` + +### Step 3 +1. Import `useRhinoState` from the `states.js` to whichever component you want to use your global state. +```js +import { useRhinoState } from './states.js` +``` +2. `useRhinoState` shares similar syntax with `useState` function. The only difference is that you pass a key to the hook instead of the initial value. +```js +const [name,setName] = useRhinoState("name") +``` + +> Note: Here "name" is the key identifying the state in the object we passed to `createRhinoState` function. + + +## Author +[Aromal Anil](https://aromalanil.tech) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f259130 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,60 @@ +{ + "name": "react-rhino", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz", + "integrity": "sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "csstype": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", + "integrity": "sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "react": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", + "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "typescript": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9779d46 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "react-rhino", + "version": "1.0.0", + "description": "A simple state management library using React context", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/aromalanil/react-rhino.git" + }, + "keywords": [ + "react-state", + "state-management", + "react-state-management", + "react-rhino", + "rhino" + ], + "author": "Aromal Anil (https://aromalanil.tech)", + "bugs": { + "url": "https://github.com/aromalanil/react-rhino/issues" + }, + "homepage": "https://github.com/aromalanil/react-rhino#readme", + "license": "MIT", + "devDependencies": { + "typescript": "^4.2.2" + }, + "dependencies": { + "@types/react": "^17.0.2", + "react": "^17.0.1" + } +} diff --git a/src/create-single-rhino-state.tsx b/src/create-single-rhino-state.tsx new file mode 100644 index 0000000..ea86c4a --- /dev/null +++ b/src/create-single-rhino-state.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { createContext, useContext, useState } from 'react'; + +export interface ProviderProps { + children: React.ReactChildren | React.ReactChild; +} + +/** + * + * Function to create a global state using React Context API, + * which returns the ContextProvider and hooks to get the + * state and updater function + * + * @param initialValue Initial value of the state + */ +function createSingleRhinoState(initialValue: T) { + //Creating Context for state value & state updater function + const ValueContext = React.createContext(initialValue); + const UpdaterContext = createContext>>(() => null); + + /** + * + * React hook which returns the stateful value + * @return Global State + */ + const useStateValue = () => useContext(ValueContext); + + /** + * + * React hook which returns a function to update the global state + * @return Global state updater function + */ + const useStateUpdate = () => useContext(UpdaterContext); + + /** + * + * Provider Component which makes the state available to all the nested components + */ + const Provider = ({ children }: ProviderProps) => { + const [state, setState] = useState(initialValue); + + return ( + + {children} + + ); + }; + + return { Provider, useStateValue, useStateUpdate }; +} +export default createSingleRhinoState; diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..1cfa9db --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import createSingleRhinoState, { ProviderProps } from './create-single-rhino-state'; + +interface StateObject { + [key: string]: any | (() => any); +} + +interface StateList { + provider: ({ children }: ProviderProps) => JSX.Element; + stateHook: () => T; + updaterHook: () => React.Dispatch>; +} + +/** + * + * Function to creates global state using rhino + * @param globalStatesObject An Object containing all the global states to be created + * @example Sample Use of createRhinoState + * const { Provider, useRhinoState }=createRhinoState({user:"John Doe",isLoggedIn:true}); + */ +const createRhinoState = (globalStatesObject: StateObject) => { + const stateListData: { [key: string]: StateList } = {}; + + Object.keys(globalStatesObject).forEach((key) => { + const initialValue = globalStatesObject[key]; + const { Provider, useStateValue, useStateUpdate } = createSingleRhinoState(initialValue); + + stateListData[key] = { + provider: Provider, + stateHook: useStateValue, + updaterHook: useStateUpdate, + }; + }); + + /** + * + * Provider Component which makes the states available to all the nested components + */ + const RhinoProvider = ({ children }: ProviderProps) => { + const stateList = Object.values(stateListData); + const listLength = stateList.length; + + let RootProvider = ({ children }: ProviderProps) => <>{children}; + + for (let i = 0; i < listLength; i++) { + const Provider = stateList[i].provider; + const OldRootProvider = RootProvider; + RootProvider = ({ children }) => ( + + {children} + + ); + } + return {children}; + }; + + /** + * + * React hook which returns the stateful value + * corresponding to the key provided + * @param key Key which represents the state + */ + const useRhinoValue = (key: string): T => { + if (key in stateListData) { + return stateListData[key].stateHook(); + } else { + throw new Error(`${key} is an invalid key`); + } + }; + + /** + * + * React hook which returns a function to update the global state + * corresponding to the key provided + * @param key Key which represents the state + */ + const useSetRhinoState = ( + key: string, + ): React.Dispatch> => { + if (key in stateListData) { + return stateListData[key].updaterHook(); + } else { + throw new Error(`${key} is an invalid key`); + } + }; + + /** + * + * React hook which returns the stateful value corresponding to the key + * provided and a function to update it + * @param key Key which represents the state + */ + const useRhinoState = ( + key: string, + ): [T, React.Dispatch>] => { + if (key in stateListData) { + return [useRhinoValue(key), useSetRhinoState(key)]; + } else { + throw new Error(`${key} is an invalid key`); + } + }; + + return { RhinoProvider, useRhinoState, useRhinoValue, useSetRhinoState }; +}; + +export default createRhinoState; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..85739b6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "strict": true, + "jsx": "react" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/__tests__/*"] +}