├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── docs ├── .gitignore ├── README.md ├── _config.yml ├── babel.config.js ├── blog │ ├── 2019-05-28-first-blog-post.md │ ├── 2019-05-29-long-blog-post.md │ ├── 2021-08-01-mdx-blog-post.mdx │ ├── 2021-08-26-welcome │ │ ├── docusaurus-plushie-banner.jpeg │ │ └── index.md │ └── authors.yml ├── docs │ ├── Hooks │ │ ├── use-holmes-reducer.md │ │ ├── use-holmes-selector.md │ │ ├── use-holmes-state.md │ │ ├── use-holmes-value.md │ │ └── use-set-holmes-reducer.md │ └── Introduction │ │ ├── getting-started.md │ │ └── motivation.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ └── img │ │ ├── decentralized.svg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── reactLogo.jpg │ │ ├── rocket.svg │ │ ├── sync.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── yarn.lock ├── example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.js │ ├── components │ │ ├── Component1.jsx │ │ ├── Component2.jsx │ │ └── Component3.jsx │ ├── holmes.js │ ├── hooks │ │ ├── index.js │ │ ├── useHolmesReducer │ │ │ └── useHolmesReducer.js │ │ ├── useHolmesSelector │ │ │ └── useHolmesSelector.js │ │ ├── useHolmesState │ │ │ └── useHolmesState.js │ │ ├── useHolmesValue │ │ │ └── useHolmesValue.js │ │ ├── useObservable │ │ │ └── useObservable.js │ │ └── useSetHolmesReducer │ │ │ └── useSetHolmesReducer.js │ ├── index.css │ ├── index.js │ ├── reportWebVitals.js │ └── utils │ │ └── Utils.js └── yarn.lock ├── package-lock.json ├── package.json ├── src ├── holmes.js ├── hooks │ ├── index.js │ ├── useHolmesReducer │ │ └── useHolmesReducer.js │ ├── useHolmesSelector │ │ └── useHolmesSelector.js │ ├── useHolmesState │ │ └── useHolmesState.js │ ├── useHolmesValue │ │ └── useHolmesValue.js │ ├── useObservable │ │ └── useObservable.js │ └── useSetHolmesReducer │ │ └── useSetHolmesReducer.js ├── index.js └── utils │ └── Utils.js └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [riktar] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | out 4 | gen 5 | node_modules 6 | dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Holmes 🔍 - Elementary State Orchestrator for React 2 | 🔍 Holmes is a 0 config, fast and elementary state orchestrator for React. 3 | 4 | Holmes has a very minimal API. It is as simple to use as React’s integrated hooks, but all state is globally accessible. 5 | 6 | 💡 Easy as React state hooks 7 | 8 | 🔄 State synchronization between components 9 | 10 | 🛰️ Distributed and not centralized state 11 | 12 | 🤯 No mutable objects 13 | 14 | 🚀 Fast 15 | 16 | ## Installation 17 | `npm i @devx-os/react-holmes` or `yarn add @devx-os/react-holmes` 18 | 19 | ## Docs 20 | 21 | https://devx-os.github.io/react-holmes/ 22 | 23 | ## Why Holmes? 24 | Holmes has one objective: **make state management as easy and fast as possible.** 25 | 26 | React components has a built-in state object, where you store property values that belongs to the component. 27 | 28 | When the state object changes, the component re-renders. 29 | 30 | This behaviour has certain limitations: 31 | 32 | Component state can only be shared by pushing it up to the common ancestor, but this might include a huge tree that then needs to re-render. 33 | React-Holmes adopts a new vision when talking about state handling. 34 | 35 | As other state managers use an external single source of truth to hydrate app client on state change, **React-Holmes does not create an external store** and does not need to wrap your app in a context. 36 | 37 | ## So, where is the global state? 38 | There is no global state, actually. 39 | 40 | The state is decentralized into components themselves. 41 | 42 | The ONLY differences are the hook declared for state management and a key to identify state chunk. 43 | 44 | While to declare a React state we need to declare it as: 45 | 46 | const [state, setState] = React.useState('test'); 47 | 48 | with React-Holmes we need to declare it as: 49 | 50 | const [state, setState] = useHolmesState('key', 'test'); 51 | 52 | 53 | ## Hooks 54 | 55 | ### useHolmesState 56 | 57 | Create a global state that can be observed by other components, this hook return the state and the setter function. 58 | 59 | `const [state, setState] = useHolmesState(key, initialState)` 60 | 61 | Example: 62 | 63 | ```js 64 | import React from 'react'; 65 | import { useHolmesState } from '@devx-os/react-holmes'; 66 | 67 | function MyComponent(props) { 68 | 69 | // useHolmesState returns an observable state value 70 | const [state, setState] = useHolmesState('myFirstGlobalState', 'initial value'); 71 | 72 | return ( 73 | <> 74 | {state} 75 | 76 | 77 | ); 78 | } 79 | 80 | export default MyComponent; 81 | 82 | ``` 83 | 84 | ### useHolmesValue 85 | 86 | Get only the value of a global state by the state key. 87 | 88 | `const value = useHolmesValue(key)` 89 | 90 | Example: 91 | 92 | ```js 93 | import React from 'react'; 94 | import { useHolmesValue } from '@devx-os/react-holmes'; 95 | 96 | function MyComponent2(props) { 97 | 98 | // access the value of the global state setted with useHolmesState 99 | const value = useHolmesValue('myFirstGlobalState'); 100 | 101 | return ( 102 | <> 103 | {value} 104 | 105 | ); 106 | } 107 | 108 | export default MyComponent2; 109 | 110 | ``` -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: 5 | name: Gao Wei 6 | title: Docusaurus Core Team 7 | url: https://github.com/wgao19 8 | image_url: https://github.com/wgao19.png 9 | tags: [hola, docusaurus] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | -------------------------------------------------------------------------------- /docs/blog/2019-05-29-long-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: long-blog-post 3 | title: Long Blog Post 4 | authors: endi 5 | tags: [hello, docusaurus] 6 | --- 7 | 8 | This is the summary of a very long blog post, 9 | 10 | Use a `` comment to limit blog post size in the list view. 11 | 12 | 13 | 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 15 | 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 17 | 18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 21 | 22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 23 | 24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 25 | 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 27 | 28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 29 | 30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 31 | 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 33 | 34 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 35 | 36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 37 | 38 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 39 | 40 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 41 | 42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 43 | 44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 45 | -------------------------------------------------------------------------------- /docs/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ```js 15 | 16 | ``` 17 | 18 | 19 | 20 | ::: 21 | -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Simply add Markdown files (or folders) to the `blog` directory. 11 | 12 | Regular blog authors can be added to `authors.yml`. 13 | 14 | The blog post date can be extracted from filenames, such as: 15 | 16 | - `2019-05-30-welcome.md` 17 | - `2019-05-30-welcome/index.md` 18 | 19 | A blog post folder can be convenient to co-locate blog post images: 20 | 21 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 22 | 23 | The blog supports tags as well! 24 | 25 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 26 | -------------------------------------------------------------------------------- /docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | endi: 2 | name: Endilie Yacop Sucipto 3 | title: Maintainer of Docusaurus 4 | url: https://github.com/endiliey 5 | image_url: https://github.com/endiliey.png 6 | 7 | yangshun: 8 | name: Yangshun Tay 9 | title: Front End Engineer @ Facebook 10 | url: https://github.com/yangshun 11 | image_url: https://github.com/yangshun.png 12 | 13 | slorber: 14 | name: Sébastien Lorber 15 | title: Docusaurus maintainer 16 | url: https://sebastienlorber.com 17 | image_url: https://github.com/slorber.png 18 | -------------------------------------------------------------------------------- /docs/docs/Hooks/use-holmes-reducer.md: -------------------------------------------------------------------------------- 1 | # useHolmesReducer 2 | 3 | ```jsx title="App.js" 4 | import "./styles.css"; 5 | import {useSetHolmesReducer, useHolmesState} from "@devx-os/react-holmes"; 6 | import MyCount from "./MyCount"; 7 | import MyCount2 from "./MyCount2"; 8 | import MyCountVisulizer from "./MyCountVisualizer"; 9 | 10 | const reducer = (state, action) => { 11 | const actions = { 12 | INCREMENT: () => { 13 | return { 14 | ...state, 15 | counter: state.counter + 1 16 | }; 17 | }, 18 | DECREMENT: () => { 19 | return { 20 | ...state, 21 | counter: state.counter - 1 22 | }; 23 | }, 24 | RESET: () => { 25 | return { 26 | counter: 0 27 | }; 28 | } 29 | }; 30 | return action ? actions[action.type]() : null; 31 | }; 32 | 33 | export default function App() { 34 | useSetHolmesReducer( 35 | "myReducer", 36 | { 37 | counter: 0 38 | }, 39 | reducer 40 | ); 41 | 42 | const [globalState, setGlobalState] = useHolmesState("myValue"); 43 | return ( 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | ); 52 | } 53 | ``` 54 | 55 | ```jsx title="MyCount.js" 56 | import "./styles.css"; 57 | import {useHolmesReducer} from "@devx-os/react-holmes"; 58 | 59 | export default function MyCount() { 60 | const [state, dispatch] = useHolmesReducer("myReducer"); 61 | return ( 62 |
63 |

{state?.counter}

64 | 65 | 66 |
67 | ); 68 | } 69 | ``` 70 | 71 | ```jsx title="MyCount2.js" 72 | import "./styles.css"; 73 | import {useHolmesReducer} from "@devx-os/react-holmes"; 74 | 75 | export default function MyCount() { 76 | const [state, dispatch] = useHolmesReducer("myReducer"); 77 | return ( 78 |
79 |

{state?.counter}

80 | 81 | 82 |
83 | ); 84 | } 85 | ``` 86 | 87 | ```jsx title="MyCountVisualizer.js" 88 | import "./styles.css"; 89 | import {useHolmesValue} from "@devx-os/react-holmes"; 90 | 91 | export default function MyCountVisulizer() { 92 | const state = useHolmesValue("myReducer"); 93 | return ( 94 |
95 |

I can access only to count value:

96 | {JSON.stringify(state, null, 2)} 97 |
98 | ); 99 | } 100 | ``` 101 | 102 | ## Examples 103 | 104 | [Count increment/decrement](https://codesandbox.io/s/react-holmes-reducer-ylih4u?file=/src/MyCount.js) -------------------------------------------------------------------------------- /docs/docs/Hooks/use-holmes-selector.md: -------------------------------------------------------------------------------- 1 | # useHolmesSelector 2 | 3 | ```mdx title="src/pages/hooks/use-holmes-state.md" 4 | # My Markdown page 5 | 6 | This is a Markdown page 7 | ``` -------------------------------------------------------------------------------- /docs/docs/Hooks/use-holmes-state.md: -------------------------------------------------------------------------------- 1 | # useHolmesState 2 | 3 | Create a global state that can be observed by other components, this hook return the state and the setter function. 4 | 5 | ```jsx title="useHolmesState" 6 | const [state, setState] = useHolmesState(key, initialState); 7 | ``` 8 | 9 | ```jsx title="Example" 10 | import React from 'react'; 11 | import {useHolmesState} from 'react-holmes/Hooks'; 12 | 13 | function MyComponent(props) { 14 | 15 | // useHolmesState returns an observable state value 16 | const [state, setState] = useHolmesState('myFirstGlobalState', 'initial value'); 17 | 18 | return ( 19 | <> 20 | {state} 21 | 22 | 23 | ); 24 | } 25 | 26 | export default MyComponent; 27 | ``` -------------------------------------------------------------------------------- /docs/docs/Hooks/use-holmes-value.md: -------------------------------------------------------------------------------- 1 | # useHolmesValue 2 | 3 | Get only the value of a global state by the state key. 4 | 5 | ```jsx title="useHolmesValue" 6 | const [state, setState] = useHolmesValue(key); 7 | ``` 8 | 9 | ```jsx title="Example" 10 | import React from 'react'; 11 | import {useHolmesValue} from 'react-holmes/Hooks'; 12 | 13 | function MyComponent(props) { 14 | 15 | // access the value of the global state setted with useHolmesState 16 | const value = useHolmesValue('myFirstGlobalState'); 17 | 18 | return ( 19 | <> 20 | {value} 21 | 22 | ); 23 | } 24 | 25 | export default MyComponent; 26 | ``` -------------------------------------------------------------------------------- /docs/docs/Hooks/use-set-holmes-reducer.md: -------------------------------------------------------------------------------- 1 | # useSetHolmesReducer 2 | 3 | Create a global state that can be observed by other components, this hook return the state and the setter function. 4 | 5 | ```mdx title="src/pages/hooks/use-set-holmes-reducer.md" 6 | # My Markdown page 7 | 8 | This is a Markdown page 9 | ``` -------------------------------------------------------------------------------- /docs/docs/Introduction/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | # Getting started 5 | 6 | 🔍 **Holmes** is a 0 config, fast and elementary state orchestrator for React. 7 | 8 | **Holmes** has a very minimal API. It is as simple to use as React’s integrated hooks, but all state is globally accessible. 9 | 10 | - 💡 Easy as React state hooks 11 | - 🔄 State synchronization between components 12 | - 🛰️ Distributed and not centralized state 13 | - 🤯 No mutable objects 14 | - 🚀 Fast 15 | 16 | ### Why Holmes? 17 | 18 | Holmes has one objective: **make state management as easy and fast as possible**. 19 | 20 | Simplifying the state management process is a great way to improve the performance of your application and with Holmes we can achieve this result. 21 | 22 | Holmes is built on top of RxJS, which is a library for reactive programming and we are using it to make the state management process as fast as possible. 23 | 24 | No external configuration is required, no boilerplate code, install the npm package and you are ready to use Holmes's elementary hooks. 25 | 26 | ### Getting Started 27 | 28 | ``` 29 | npm -i @devx-os/react-holmes 30 | ``` 31 | or 32 | ``` 33 | yarn add @devx-os/react-holmes 34 | ``` 35 | 36 | ### What you'll need 37 | 38 | - [Node.js](https://nodejs.org/en/download/) version 14 or above: 39 | - When installing Node.js, you are recommended to check all checkboxes related to dependencies. -------------------------------------------------------------------------------- /docs/docs/Introduction/motivation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | --- 4 | 5 | # Motivation 6 | 7 | *React* components has a built-in state object, where you store property values that belongs to the component. 8 | 9 | When the state object changes, the component re-renders. 10 | 11 | This behaviour has certain limitations: 12 | 13 | - Component state can only be shared by pushing it up to the common ancestor, but this might include a huge tree that then needs to re-render. 14 | 15 | **React-Holmes** adopts a new vision when talking about state handling. 16 | 17 | As other state managers use an external single source of truth to hydrate app client on state change, **React-Holmes** does not create an external store and does not need to wrap your app in a context. 18 | 19 | ### So, where is the global state? 20 | 21 | There is no global state, actually. 22 | The state is decentralized into components themselves. 23 | 24 | The *ONLY* differences are the hook declared for state management and a *key* to identify state chunk. 25 | 26 | 27 | While to declare a React state we need to declare it as: 28 | ```jsx 29 | const [state, setState] = React.useState('test'); 30 | ``` 31 | with React-Holmes we need to declare it as: 32 | ```jsx 33 | const [state, setState] = useHolmesState('key', 'test'); 34 | ``` 35 | 36 | ### What does *useHolmesState* do? 37 | 38 | When a state is set with *useHolmesState*, the hook is responsible to set state with React and add some power ups to it. 39 | 40 | In fact, when a component using *useHolmesState* is mounted, it will create (if not exists) a new *RxJs Observable* identified by specified key. 41 | 42 | If we register another component with the same key, it will not create another Observable, instead it will subscribe to existing one, allowing access to its value and its callback to mutate the state. 43 | 44 | This allows state handling to be delegated to React itself, guaranteeing reactish updates and shared state too. 45 | 46 | So **React-Holmes** can be considered a *React state orchestrator* because its hooks can be used into very small and deeply nested components without performance impacts generated by multiple re-renders. 47 | 48 | 49 | - 🚀 Easy as React state management 50 | - 🔄 State synchronization between components 51 | - 🛰️ Distributed and not centralized state 52 | - 🤯 No extra re-renders 53 | - 🚀 Fast -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 5 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 6 | 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: 'React Holmes', 10 | tagline: 'Elementary State Orchestrator for React', 11 | url: 'https://github.com/devx-os', 12 | baseUrl: '/react-holmes/', 13 | onBrokenLinks: 'throw', 14 | onBrokenMarkdownLinks: 'warn', 15 | favicon: 'img/favicon.ico', 16 | trailingSlash: false, 17 | // GitHub pages deployment config. 18 | // If you aren't using GitHub pages, you don't need these. 19 | organizationName: 'devx-os', // Usually your GitHub org/user name. 20 | projectName: 'react-holmes', // Usually your repo name. 21 | 22 | // Even if you don't use internalization, you can use this field to set useful 23 | // metadata like html lang. For example, if your site is Chinese, you may want 24 | // to replace "en" with "zh-Hans". 25 | // i18n: { 26 | // defaultLocale: 'en', 27 | // locales: ['en'], 28 | // }, 29 | 30 | presets: [ 31 | [ 32 | 'classic', 33 | /** @type {import('@docusaurus/preset-classic').Options} */ 34 | ({ 35 | docs: { 36 | sidebarPath: require.resolve('./sidebars.js'), 37 | // Please change this to your repo. 38 | // Remove this to remove the "edit this page" links. 39 | editUrl: 40 | 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', 41 | }, 42 | blog: { 43 | showReadingTime: true, 44 | // Please change this to your repo. 45 | // Remove this to remove the "edit this page" links. 46 | editUrl: 47 | 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', 48 | }, 49 | theme: { 50 | customCss: require.resolve('./src/css/custom.css'), 51 | }, 52 | }), 53 | ], 54 | ], 55 | 56 | themeConfig: 57 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 58 | ({ 59 | navbar: { 60 | title: 'React Holmes - Elementary State Orchestrator', 61 | logo: { 62 | alt: 'React Holmes - Elementary State Orchestrator', 63 | src: 'img/reactLogo.jpg', 64 | }, 65 | items: [ 66 | // { 67 | // type: 'doc', 68 | // docId: 'intro', 69 | // position: 'left', 70 | // label: 'Tutorial', 71 | // }, 72 | // { to: '/blog', label: 'Blog', position: 'left' }, 73 | { 74 | href: 'https://github.com/devx-os/react-holmes', 75 | label: 'GitHub', 76 | position: 'right', 77 | }, 78 | ], 79 | }, 80 | footer: { 81 | style: 'dark', 82 | links: [ 83 | { 84 | title: 'Introduction', 85 | items: [ 86 | { 87 | label: 'Getting started', 88 | to: '/docs/Introduction/getting-started', 89 | }, 90 | { 91 | label: 'Motivation', 92 | to: '/docs/Introduction/motivation', 93 | }, 94 | ], 95 | }, 96 | { 97 | title: 'Hooks', 98 | items: [ 99 | { 100 | label: 'useHolmesReducer', 101 | to: '/docs/Hooks/use-holmes-reducer', 102 | }, 103 | { 104 | label: 'useHolmesSelector', 105 | to: '/docs/Hooks/use-holmes-selector', 106 | }, 107 | { 108 | label: 'useHolmesState', 109 | to: '/docs/Hooks/use-holmes-state', 110 | }, 111 | { 112 | label: 'useHolmesValue', 113 | to: '/docs/Hooks/use-holmes-value', 114 | }, 115 | { 116 | label: 'useSetHolmesReducer', 117 | to: '/docs/Hooks/use-set-holmes-reducer', 118 | } 119 | ], 120 | }, 121 | // { 122 | // title: 'Community', 123 | // items: [ 124 | // { 125 | // label: 'Stack Overflow', 126 | // href: 'https://stackoverflow.com/questions/tagged/docusaurus', 127 | // }, 128 | // { 129 | // label: 'Discord', 130 | // href: 'https://discordapp.com/invite/docusaurus', 131 | // }, 132 | // { 133 | // label: 'Twitter', 134 | // href: 'https://twitter.com/docusaurus', 135 | // }, 136 | // ], 137 | // }, 138 | { 139 | title: 'More', 140 | items: [ 141 | { 142 | label: 'GitHub', 143 | href: 'https://github.com/devx-os/react-holmes', 144 | }, 145 | ], 146 | }, 147 | ], 148 | copyright: `Copyright © ${new Date().getFullYear()} React-Holmes documentation. Built with Docusaurus.`, 149 | }, 150 | prism: { 151 | theme: lightCodeTheme, 152 | darkTheme: darkCodeTheme, 153 | }, 154 | }), 155 | }; 156 | 157 | module.exports = config; 158 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.0.0-beta.20", 18 | "@docusaurus/preset-classic": "2.0.0-beta.20", 19 | "@mdx-js/react": "^1.6.22", 20 | "clsx": "^1.1.1", 21 | "prism-react-renderer": "^1.3.1", 22 | "react": "^17.0.2", 23 | "react-dom": "^17.0.2" 24 | }, 25 | "devDependencies": { 26 | "@docusaurus/module-type-aliases": "2.0.0-beta.20" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.5%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: 18 | // [{type: 'autogenerated', dirName: '.'}], 19 | [ 20 | { 21 | type: 'category', 22 | label: 'Introduction', 23 | items: ['Introduction/motivation', 'Introduction/getting-started'], 24 | }, 25 | { 26 | type: 'category', 27 | label: 'Hooks', 28 | items: ['Hooks/use-holmes-state', 'Hooks/use-holmes-value', 'Hooks/use-holmes-reducer', 'Hooks/use-holmes-selector', 'Hooks/use-set-holmes-reducer'], 29 | }, 30 | ] 31 | 32 | // But you can create a sidebar manually 33 | /* 34 | tutorialSidebar: [ 35 | { 36 | type: 'category', 37 | label: 'Tutorial', 38 | items: ['hello'], 39 | }, 40 | ], 41 | */ 42 | }; 43 | 44 | module.exports = sidebars; 45 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | const FeatureList = [ 6 | { 7 | title: 'Fast and easy to use', 8 | Svg: require('@site/static/img/rocket.svg').default, 9 | description: ( 10 | <> 11 | Easy integration in your react project. Just install the package 12 | and use it. No contexts. No wrappers. Only observers. 13 | 14 | ), 15 | }, 16 | { 17 | title: 'Distributed state', 18 | Svg: require('@site/static/img/decentralized.svg').default, 19 | description: ( 20 | <> 21 | Application state is decentralized between components, making 22 | it accessible in every part of application. 23 | 24 | ), 25 | }, 26 | { 27 | title: 'State sync', 28 | Svg: require('@site/static/img/sync.svg').default, 29 | description: ( 30 | <> 31 | Components will share a local copy of the state and the callback to modify it. 32 | Every update will send and event to components subscribed to same state. 33 | 34 | ), 35 | }, 36 | ]; 37 | 38 | function Feature({Svg, title, description}) { 39 | return ( 40 |
41 |
42 | 43 |
44 |
45 |

{title}

46 |

{description}

47 |
48 |
49 | ); 50 | } 51 | 52 | export default function HomepageFeatures() { 53 | return ( 54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => ( 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #0ea5e9; 10 | --ifm-color-primary-dark: #075985; 11 | --ifm-color-primary-darker: #0c4a6e; 12 | --ifm-color-primary-darkest: #0c4a6e; 13 | --ifm-color-primary-light: #1e3a8a; 14 | --ifm-color-primary-lighter: #60a5fa; 15 | --ifm-color-primary-lightest: #bfdbfe; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #0ea5e9; 23 | --ifm-color-primary-dark: #075985; 24 | --ifm-color-primary-darker: #0c4a6e; 25 | --ifm-color-primary-darkest: #0c4a6e; 26 | --ifm-color-primary-light: #1e3a8a; 27 | --ifm-color-primary-lighter: #60a5fa; 28 | --ifm-color-primary-lightest: #bfdbfe; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Layout from '@theme/Layout'; 4 | import Link from '@docusaurus/Link'; 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 6 | import styles from './index.module.css'; 7 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 8 | 9 | function HomepageHeader() { 10 | const { siteConfig } = useDocusaurusContext(); 11 | return ( 12 |
13 |
14 |

{siteConfig.title}

15 |

{siteConfig.tagline}

16 |
17 | 21 | Get started - 2min 22 | 23 |
24 |
25 |
26 | ); 27 | } 28 | 29 | export default function Home() { 30 | const { siteConfig } = useDocusaurusContext(); 31 | return ( 32 | 36 | 37 |
38 | 39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/decentralized.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/docs/static/img/docusaurus.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/reactLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/docs/static/img/reactLogo.jpg -------------------------------------------------------------------------------- /docs/static/img/rocket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 14 | 17 | 20 | 21 | 23 | 25 | 26 | 29 | 30 | 32 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/static/img/sync.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_mountain.svg: -------------------------------------------------------------------------------- 1 | 2 | Easy to Use 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_react.svg: -------------------------------------------------------------------------------- 1 | 2 | Powered by React 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | 2 | Focus on What Matters 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.2.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.1.0", 10 | "react-dom": "^18.1.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/example/public/logo192.png -------------------------------------------------------------------------------- /example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devx-os/react-holmes/49d3214620e1c48823b701d82737b650225d4ca8/example/public/logo512.png -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import Component1 from "./components/Component1"; 2 | import Component2 from "./components/Component2"; 3 | import Component3 from "./components/Component3"; 4 | import {useSetHolmesReducer} from "./hooks"; 5 | 6 | const reducer = (state, action) => { 7 | const actions = { 8 | INCREMENT: () => { 9 | return { 10 | ...state, 11 | counter: state.counter + 1 12 | }; 13 | }, 14 | DECREMENT: () => { 15 | return { 16 | ...state, 17 | counter: state.counter - 1 18 | }; 19 | }, 20 | RESET: () => { 21 | return { 22 | counter: 0 23 | }; 24 | } 25 | }; 26 | return action ? actions[action.type]() : null; 27 | }; 28 | 29 | function App() { 30 | const [state, dispatch] = useSetHolmesReducer( 31 | "myReducer", 32 | { 33 | counter: 0 34 | }, 35 | reducer 36 | ); 37 | 38 | return ( 39 | <> 40 | {JSON.stringify(state)} 41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 | ); 51 | } 52 | 53 | export default App; 54 | -------------------------------------------------------------------------------- /example/src/components/Component1.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHolmesState } from "../hooks/index"; 3 | 4 | const Component1 = () => { 5 | const [state, setState] = useHolmesState("component-1",0); 6 | console.log(state, "state compoennt 1"); 7 | return ( 8 |
9 |

Component1

10 |

State : {state}

11 |
12 | ); 13 | }; 14 | 15 | export default Component1; 16 | -------------------------------------------------------------------------------- /example/src/components/Component2.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHolmesState } from "../hooks"; 3 | 4 | const Component2 = () => { 5 | const [, setState] = useHolmesState("component-1"); 6 | return ( 7 | 10 | ); 11 | }; 12 | 13 | export default Component2; 14 | -------------------------------------------------------------------------------- /example/src/components/Component3.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {useHolmesReducer, useHolmesState, useHolmesValue} from "../hooks"; 3 | 4 | const Component3 = () => { 5 | const [reducerState, dispatch] = useHolmesReducer('myReducer'); 6 | const [state, setState] = useHolmesState("component-1"); 7 | return ( 8 |
9 |

Component3

10 |

Component 1 State : {state}

11 | 12 | 13 | {reducerState?.counter} 14 |
15 | ); 16 | }; 17 | 18 | export default Component3; 19 | -------------------------------------------------------------------------------- /example/src/holmes.js: -------------------------------------------------------------------------------- 1 | let $global = null; 2 | $global = 3 | typeof global !== 'undefined' 4 | ? global 5 | : typeof window !== 'undefined' 6 | ? window 7 | : {}; 8 | 9 | if (!$global) throw new Error('Global object not found.'); 10 | 11 | $global.__holmesObservableMap = new Map(); 12 | 13 | export function getGlobalContext() { 14 | return $global.__holmesObservableMap; 15 | } 16 | 17 | export function getObservable(key) { 18 | if (!$global.__holmesObservableMap.has(key)) { 19 | return null; 20 | } 21 | return $global.__holmesObservableMap.get(key); 22 | } 23 | -------------------------------------------------------------------------------- /example/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import useHolmesState from "./useHolmesState/useHolmesState"; 2 | import useHolmesSelector from "./useHolmesSelector/useHolmesSelector"; 3 | import useHolmesValue from "./useHolmesValue/useHolmesValue"; 4 | import useSetHolmesReducer from "./useSetHolmesReducer/useSetHolmesReducer"; 5 | import useHolmesReducer from "./useHolmesReducer/useHolmesReducer"; 6 | export { 7 | useHolmesState, 8 | useHolmesSelector, 9 | useHolmesValue, 10 | useSetHolmesReducer, 11 | useHolmesReducer, 12 | }; 13 | -------------------------------------------------------------------------------- /example/src/hooks/useHolmesReducer/useHolmesReducer.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | import { getGlobalContext } from "../../holmes"; 4 | import useHolmesValue from "../useHolmesValue/useHolmesValue"; 5 | 6 | const useHolmesReducer = (key = "") => { 7 | const dispatch = useRef(() => {}); 8 | 9 | useEffect(() => { 10 | const context = getGlobalContext(); 11 | const dispatchGlobal = context.has(`${key}_dispatch`) 12 | ? context.get(`${key}_dispatch`) 13 | : null; 14 | dispatch.current = dispatchGlobal; 15 | }, []); 16 | 17 | const state = useHolmesValue(key); 18 | 19 | return [state, dispatch.current]; 20 | }; 21 | 22 | export default useHolmesReducer; 23 | -------------------------------------------------------------------------------- /example/src/hooks/useHolmesSelector/useHolmesSelector.js: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | import { getGlobalContext } from '../../holmes'; 3 | 4 | const useHolmesSelector = (keys = []) => { 5 | const [mapper, setMapper] = useState({}); 6 | 7 | useEffect(() => { 8 | const context = getGlobalContext(); 9 | const unSubscribers = []; 10 | keys.forEach((key) => { 11 | if (context.has(key)) { 12 | const observable = context.get(key); 13 | const subscription = observable.subscribe((data) => { 14 | setMapper((prev) => ({ 15 | ...prev, 16 | [key]: data, 17 | })); 18 | }); 19 | unSubscribers.push(subscription); 20 | } 21 | }); 22 | return () => { 23 | if (unSubscribers.length > 0) { 24 | try { 25 | unSubscribers.forEach((s) => s.unsubscribe()); 26 | } catch (e) { 27 | console.log(e); 28 | } 29 | } 30 | }; 31 | }, []); 32 | 33 | return mapper; 34 | }; 35 | 36 | export default useHolmesSelector; 37 | -------------------------------------------------------------------------------- /example/src/hooks/useHolmesState/useHolmesState.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | 3 | import { onCheckKeyIfPresent } from "../../utils/Utils"; 4 | import { getGlobalContext } from "../../holmes"; 5 | import {useHolmesValue} from "../index"; 6 | 7 | const useHolmesState = (key = "", initialState = undefined) => { 8 | onCheckKeyIfPresent(key); 9 | 10 | const tempState = useHolmesValue(key, initialState); 11 | 12 | const setState = useCallback( 13 | (value) => { 14 | const context = getGlobalContext(); 15 | const observable = context.get(key); 16 | if (observable) { 17 | if (typeof value === "function") { 18 | observable.next(value(tempState)); 19 | } else { 20 | observable.next(value); 21 | } 22 | } 23 | }, 24 | [tempState] 25 | ); 26 | 27 | return [tempState, setState]; 28 | }; 29 | 30 | export default useHolmesState; 31 | -------------------------------------------------------------------------------- /example/src/hooks/useHolmesValue/useHolmesValue.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect, useRef} from 'react'; 2 | 3 | import useObservable from "../useObservable/useObservable"; 4 | 5 | const useHolmesValue = (key = '', initialState) => { 6 | const [value,setValue] = useState(initialState); 7 | const subRef = useRef(null); 8 | const observable = useObservable(key, initialState); 9 | 10 | if (observable) { 11 | subRef.current = observable.subscribe((data) => { 12 | if(data !== undefined && data !== value){ 13 | setValue(data); 14 | } 15 | }); 16 | } 17 | 18 | useEffect(() => { 19 | return () => { 20 | if (subRef.current) { 21 | subRef.current.unsubscribe(); 22 | } 23 | }; 24 | }, []); 25 | 26 | return value; 27 | }; 28 | 29 | export default useHolmesValue; 30 | -------------------------------------------------------------------------------- /example/src/hooks/useObservable/useObservable.js: -------------------------------------------------------------------------------- 1 | import {BehaviorSubject} from "rxjs"; 2 | 3 | import {getGlobalContext} from '../../holmes'; 4 | 5 | const useObservable = (key = '', initialState) => { 6 | const context = getGlobalContext(); 7 | 8 | if (!context.has(key)) { 9 | const observable = new BehaviorSubject(initialState); 10 | context.set(key, observable); 11 | } 12 | 13 | return context.get(key); 14 | }; 15 | 16 | export default useObservable; 17 | -------------------------------------------------------------------------------- /example/src/hooks/useSetHolmesReducer/useSetHolmesReducer.js: -------------------------------------------------------------------------------- 1 | import { useReducer, useState, useEffect } from "react"; 2 | import { BehaviorSubject } from "rxjs"; 3 | import { onCheckKeyIfPresent } from "../../utils/Utils"; 4 | import { getGlobalContext } from "../../holmes"; 5 | 6 | const useSetHolmesReducer = (key = "", initialState = null, reducer) => { 7 | onCheckKeyIfPresent(key); 8 | 9 | const [tempState, setTempState] = useState(); 10 | const [state, dispatch] = useReducer(reducer, initialState); 11 | 12 | useEffect(() => { 13 | const context = getGlobalContext(); 14 | let subscription = null; 15 | let observable = null; 16 | 17 | observable = new BehaviorSubject(initialState); 18 | context.set(key, observable); 19 | context.set(`${key}_dispatch`, dispatch); 20 | if (observable) { 21 | subscription = observable.subscribe((data) => setTempState(data)); 22 | } 23 | return () => { 24 | const context = getGlobalContext(); 25 | const observable = context.get(key); 26 | if (observable && subscription) { 27 | subscription.unsubscribe(); 28 | } 29 | }; 30 | }, []); 31 | 32 | useEffect(() => { 33 | const context = getGlobalContext(); 34 | const observable = context.get(key); 35 | observable.next(state); 36 | }, [state]); 37 | 38 | return [tempState, dispatch]; 39 | }; 40 | 41 | export default useSetHolmesReducer; 42 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /example/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /example/src/utils/Utils.js: -------------------------------------------------------------------------------- 1 | export const initBehaviorSubjectState = (value) => { 2 | if (typeof value === "function") { 3 | return value.call(); 4 | } 5 | return value; 6 | }; 7 | 8 | export const onCheckKeyIfPresent = (key) => { 9 | if (!key || key === "") { 10 | throw new Error("Key Parameter is required"); 11 | } 12 | return key; 13 | }; 14 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devx-os/react-holmes", 3 | "version": "0.2.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@devx-os/react-holmes", 9 | "version": "0.2.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "react": "^18.1.0", 13 | "react-dom": "^18.1.0", 14 | "rxjs": "^7.5.5" 15 | }, 16 | "peerDependencies": { 17 | "react": ">=16", 18 | "rxjs": ">=6" 19 | } 20 | }, 21 | "node_modules/js-tokens": { 22 | "version": "4.0.0", 23 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 24 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 25 | "dev": true 26 | }, 27 | "node_modules/loose-envify": { 28 | "version": "1.4.0", 29 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 30 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 31 | "dev": true, 32 | "dependencies": { 33 | "js-tokens": "^3.0.0 || ^4.0.0" 34 | }, 35 | "bin": { 36 | "loose-envify": "cli.js" 37 | } 38 | }, 39 | "node_modules/react": { 40 | "version": "18.1.0", 41 | "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", 42 | "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", 43 | "dev": true, 44 | "dependencies": { 45 | "loose-envify": "^1.1.0" 46 | }, 47 | "engines": { 48 | "node": ">=0.10.0" 49 | } 50 | }, 51 | "node_modules/react-dom": { 52 | "version": "18.1.0", 53 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", 54 | "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", 55 | "dev": true, 56 | "dependencies": { 57 | "loose-envify": "^1.1.0", 58 | "scheduler": "^0.22.0" 59 | }, 60 | "peerDependencies": { 61 | "react": "^18.1.0" 62 | } 63 | }, 64 | "node_modules/rxjs": { 65 | "version": "7.5.5", 66 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", 67 | "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", 68 | "dev": true, 69 | "dependencies": { 70 | "tslib": "^2.1.0" 71 | } 72 | }, 73 | "node_modules/scheduler": { 74 | "version": "0.22.0", 75 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", 76 | "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", 77 | "dev": true, 78 | "dependencies": { 79 | "loose-envify": "^1.1.0" 80 | } 81 | }, 82 | "node_modules/tslib": { 83 | "version": "2.4.0", 84 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 85 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", 86 | "dev": true 87 | } 88 | }, 89 | "dependencies": { 90 | "js-tokens": { 91 | "version": "4.0.0", 92 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 93 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 94 | "dev": true 95 | }, 96 | "loose-envify": { 97 | "version": "1.4.0", 98 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 99 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 100 | "dev": true, 101 | "requires": { 102 | "js-tokens": "^3.0.0 || ^4.0.0" 103 | } 104 | }, 105 | "react": { 106 | "version": "18.1.0", 107 | "resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz", 108 | "integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==", 109 | "dev": true, 110 | "requires": { 111 | "loose-envify": "^1.1.0" 112 | } 113 | }, 114 | "react-dom": { 115 | "version": "18.1.0", 116 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz", 117 | "integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==", 118 | "dev": true, 119 | "requires": { 120 | "loose-envify": "^1.1.0", 121 | "scheduler": "^0.22.0" 122 | } 123 | }, 124 | "rxjs": { 125 | "version": "7.5.5", 126 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", 127 | "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", 128 | "dev": true, 129 | "requires": { 130 | "tslib": "^2.1.0" 131 | } 132 | }, 133 | "scheduler": { 134 | "version": "0.22.0", 135 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz", 136 | "integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==", 137 | "dev": true, 138 | "requires": { 139 | "loose-envify": "^1.1.0" 140 | } 141 | }, 142 | "tslib": { 143 | "version": "2.4.0", 144 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", 145 | "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", 146 | "dev": true 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devx-os/react-holmes", 3 | "version": "0.2.0", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "scripts": { 10 | "set:example": "rm -rf ./example/src/hooks ./example/src/holmes.js ./example/src/utils && cp -r ./src/hooks ./example/src && cp ./src/holmes.js ./example/src && cp -r ./src/utils ./example/src", 11 | "build": "rm -rf dist && cp -r src dist;", 12 | "start:example": "cd ./example && npm start" 13 | }, 14 | "author": "Riccardo Tartaglia", 15 | "peerDependencies": { 16 | "react": ">=16", 17 | "rxjs": ">=6" 18 | }, 19 | "license": "ISC", 20 | "devDependencies": { 21 | "react": "^18.1.0", 22 | "react-dom": "^18.1.0", 23 | "rxjs": "^7.5.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/holmes.js: -------------------------------------------------------------------------------- 1 | let $global = null; 2 | $global = 3 | typeof global !== 'undefined' 4 | ? global 5 | : typeof window !== 'undefined' 6 | ? window 7 | : {}; 8 | 9 | if (!$global) throw new Error('Global object not found.'); 10 | 11 | $global.__holmesObservableMap = new Map(); 12 | 13 | export function getGlobalContext() { 14 | return $global.__holmesObservableMap; 15 | } 16 | 17 | export function getObservable(key) { 18 | if (!$global.__holmesObservableMap.has(key)) { 19 | return null; 20 | } 21 | return $global.__holmesObservableMap.get(key); 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | import useHolmesState from "./useHolmesState/useHolmesState"; 2 | import useHolmesSelector from "./useHolmesSelector/useHolmesSelector"; 3 | import useHolmesValue from "./useHolmesValue/useHolmesValue"; 4 | import useSetHolmesReducer from "./useSetHolmesReducer/useSetHolmesReducer"; 5 | import useHolmesReducer from "./useHolmesReducer/useHolmesReducer"; 6 | 7 | export { 8 | useHolmesState, 9 | useHolmesSelector, 10 | useHolmesValue, 11 | useSetHolmesReducer, 12 | useHolmesReducer, 13 | }; 14 | -------------------------------------------------------------------------------- /src/hooks/useHolmesReducer/useHolmesReducer.js: -------------------------------------------------------------------------------- 1 | import {useEffect, useRef} from 'react'; 2 | 3 | import {getGlobalContext} from '../../holmes'; 4 | import useHolmesValue from "../useHolmesValue/useHolmesValue"; 5 | 6 | const useHolmesReducer = (key = '') => { 7 | const dispatch = useRef(() => {}) 8 | 9 | useEffect(() => { 10 | const context = getGlobalContext(); 11 | const dispatchGlobal = context.has(`${key}_dispatch`) ? context.get(`${key}_dispatch`) : null; 12 | //if(!dispatchGlobal) throw new Error(`${key} has not been setted, set a reducer first with setHolmesReducer() hook`); 13 | dispatch.current = dispatchGlobal; 14 | }, []); 15 | 16 | const state = useHolmesValue(key); 17 | 18 | return [state, dispatch.current]; 19 | }; 20 | 21 | export default useHolmesReducer; 22 | -------------------------------------------------------------------------------- /src/hooks/useHolmesSelector/useHolmesSelector.js: -------------------------------------------------------------------------------- 1 | import {useEffect, useState} from 'react'; 2 | import { getGlobalContext } from '../../holmes'; 3 | 4 | const useHolmesSelector = (keys = []) => { 5 | const [mapper, setMapper] = useState({}); 6 | 7 | useEffect(() => { 8 | const context = getGlobalContext(); 9 | const unSubscribers = []; 10 | keys.forEach((key) => { 11 | if (context.has(key)) { 12 | const observable = context.get(key); 13 | const subscription = observable.subscribe((data) => { 14 | setMapper((prev) => ({ 15 | ...prev, 16 | [key]: data, 17 | })); 18 | }); 19 | unSubscribers.push(subscription); 20 | } 21 | }); 22 | return () => { 23 | if (unSubscribers.length > 0) { 24 | try { 25 | unSubscribers.forEach((s) => s.unsubscribe()); 26 | } catch (e) { 27 | console.log(e); 28 | } 29 | } 30 | }; 31 | }, []); 32 | 33 | return mapper; 34 | }; 35 | 36 | export default useHolmesSelector; 37 | -------------------------------------------------------------------------------- /src/hooks/useHolmesState/useHolmesState.js: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | 3 | import { onCheckKeyIfPresent } from "../../utils/Utils"; 4 | import { getGlobalContext } from "../../holmes"; 5 | import {useHolmesValue} from "../index"; 6 | 7 | const useHolmesState = (key = "", initialState = undefined) => { 8 | onCheckKeyIfPresent(key); 9 | 10 | const tempState = useHolmesValue(key, initialState); 11 | 12 | const setState = useCallback( 13 | (value) => { 14 | const context = getGlobalContext(); 15 | const observable = context.get(key); 16 | if (observable) { 17 | if (typeof value === "function") { 18 | observable.next(value(tempState)); 19 | } else { 20 | observable.next(value); 21 | } 22 | } 23 | }, 24 | [tempState] 25 | ); 26 | 27 | return [tempState, setState]; 28 | }; 29 | 30 | export default useHolmesState; 31 | -------------------------------------------------------------------------------- /src/hooks/useHolmesValue/useHolmesValue.js: -------------------------------------------------------------------------------- 1 | import {useState, useEffect, useRef} from 'react'; 2 | 3 | import useObservable from "../useObservable/useObservable"; 4 | 5 | const useHolmesValue = (key = '', initialState) => { 6 | const [value,setValue] = useState(initialState); 7 | const subRef = useRef(null); 8 | const observable = useObservable(key, initialState); 9 | 10 | if (observable) { 11 | subRef.current = observable.subscribe((data) => { 12 | if(data !== undefined && data !== value){ 13 | setValue(data); 14 | } 15 | }); 16 | } 17 | 18 | useEffect(() => { 19 | return () => { 20 | if (subRef.current) { 21 | subRef.current.unsubscribe(); 22 | } 23 | }; 24 | }, []); 25 | 26 | return value; 27 | }; 28 | 29 | export default useHolmesValue; 30 | -------------------------------------------------------------------------------- /src/hooks/useObservable/useObservable.js: -------------------------------------------------------------------------------- 1 | import {BehaviorSubject} from "rxjs"; 2 | 3 | import {getGlobalContext} from '../../holmes'; 4 | 5 | const useObservable = (key = '', initialState) => { 6 | const context = getGlobalContext(); 7 | 8 | if (!context.has(key)) { 9 | const observable = new BehaviorSubject(initialState); 10 | context.set(key, observable); 11 | } 12 | 13 | return context.get(key); 14 | }; 15 | 16 | export default useObservable; 17 | -------------------------------------------------------------------------------- /src/hooks/useSetHolmesReducer/useSetHolmesReducer.js: -------------------------------------------------------------------------------- 1 | import { useReducer, useState, useEffect } from "react"; 2 | import { BehaviorSubject } from "rxjs"; 3 | import { onCheckKeyIfPresent } from "../../utils/Utils"; 4 | import { getGlobalContext } from "../../holmes"; 5 | 6 | const useSetHolmesReducer = (key = "", initialState = null, reducer) => { 7 | onCheckKeyIfPresent(key); 8 | 9 | const [tempState, setTempState] = useState(); 10 | const [state, dispatch] = useReducer(reducer, initialState); 11 | 12 | useEffect(() => { 13 | const context = getGlobalContext(); 14 | let subscription = null; 15 | let observable = null; 16 | 17 | observable = new BehaviorSubject(initialState); 18 | context.set(key, observable); 19 | context.set(`${key}_dispatch`, dispatch); 20 | if (observable) { 21 | subscription = observable.subscribe((data) => setTempState(data)); 22 | } 23 | return () => { 24 | const context = getGlobalContext(); 25 | const observable = context.get(key); 26 | if (observable && subscription) { 27 | subscription.unsubscribe(); 28 | } 29 | }; 30 | }, []); 31 | 32 | useEffect(() => { 33 | const context = getGlobalContext(); 34 | const observable = context.get(key); 35 | observable.next(state); 36 | }, [state]); 37 | 38 | return [tempState, dispatch]; 39 | }; 40 | 41 | export default useSetHolmesReducer; 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const hooks = require('./hooks/index') 2 | 3 | module.exports = {...hooks} -------------------------------------------------------------------------------- /src/utils/Utils.js: -------------------------------------------------------------------------------- 1 | export const initBehaviorSubjectState = (value) => { 2 | if (typeof value === 'function') { 3 | return value.call(); 4 | } 5 | return value; 6 | }; 7 | 8 | export const onCheckKeyIfPresent = (key) => { 9 | if (!key || key === '') { 10 | throw new Error('Key Parameter is required'); 11 | } 12 | return key; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "js-tokens@^3.0.0 || ^4.0.0": 6 | "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 7 | "resolved" "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" 8 | "version" "4.0.0" 9 | 10 | "loose-envify@^1.1.0": 11 | "integrity" "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==" 12 | "resolved" "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" 13 | "version" "1.4.0" 14 | dependencies: 15 | "js-tokens" "^3.0.0 || ^4.0.0" 16 | 17 | "react-dom@^18.1.0": 18 | "integrity" "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==" 19 | "resolved" "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz" 20 | "version" "18.1.0" 21 | dependencies: 22 | "loose-envify" "^1.1.0" 23 | "scheduler" "^0.22.0" 24 | 25 | "react@^18.1.0": 26 | "integrity" "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==" 27 | "resolved" "https://registry.npmjs.org/react/-/react-18.1.0.tgz" 28 | "version" "18.1.0" 29 | dependencies: 30 | "loose-envify" "^1.1.0" 31 | 32 | "rxjs@^7.5.5": 33 | "integrity" "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==" 34 | "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz" 35 | "version" "7.5.5" 36 | dependencies: 37 | "tslib" "^2.1.0" 38 | 39 | "scheduler@^0.22.0": 40 | "integrity" "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==" 41 | "resolved" "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz" 42 | "version" "0.22.0" 43 | dependencies: 44 | "loose-envify" "^1.1.0" 45 | 46 | "tslib@^2.1.0": 47 | "integrity" "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 48 | "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" 49 | "version" "2.4.0" 50 | --------------------------------------------------------------------------------