├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── TODO
├── examples
├── README.md
└── hierarchal-lazy-loading
│ ├── index.html
│ ├── index.jsx
│ └── lib
│ └── githubDAO.js
├── jest.config.js
├── package-lock.json
├── package.json
├── src
└── index.tsx
├── test
├── __snapshots__
│ └── react-service-container.test.tsx.snap
└── react-service-container.test.tsx
├── tsconfig.json
├── tsconfig.test.json
└── webpack.config.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | examples/ linguist-documentation
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #### joe made this: https://goel.io/joe
2 |
3 | #####=== Node ===#####
4 |
5 | # Logs
6 | logs
7 | *.log
8 |
9 | # Runtime data
10 | pids
11 | *.pid
12 | *.seed
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directory
30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
31 | node_modules
32 |
33 | # Debug log from npm
34 | npm-debug.log
35 |
36 | umd/
37 | lib/
38 | .cache/
39 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *
2 | !package*.json
3 | !lib/*
4 | !umd/*
5 | !LICENSE.md
6 | !README.md
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - node
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | travis.kaufman@gmail.com.
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series
87 | of actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or
94 | permanent ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within
114 | the community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.0, available at
120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121 |
122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
123 | enforcement ladder](https://github.com/mozilla/diversity).
124 |
125 | [homepage]: https://www.contributor-covenant.org
126 |
127 | For answers to common questions about this code of conduct, see the FAQ at
128 | https://www.contributor-covenant.org/faq. Translations are available at
129 | https://www.contributor-covenant.org/translations.
130 |
131 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This is a brand new library and I personally find it useful. I would love feedback for the community and will
2 | happily accept contributions!
3 |
4 | **Please make sure you follow the [code of conduct](./CODE_OF_CONDUCT.md) when interacting with me
5 | or anyone else who contributes to this codebase**
6 |
7 | :heart:
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-present Travis Kaufman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-service-container
2 |
3 | [](https://travis-ci.org/traviskaufman/react-service-container)
4 |
5 | react-service-container is a library which helps provide services to your components and hooks
6 | in an easy, clean, and testable manner.
7 |
8 | Simply define a service
9 |
10 | ```jsx
11 | // greeter.js
12 |
13 | export default class Greeter {
14 | greet() {
15 | return "👋 Hello there!";
16 | }
17 | }
18 | ```
19 |
20 | provide it within a `ServiceContainer`
21 |
22 | ```jsx
23 | // App.js
24 | import React from "react";
25 | import { ServiceContainer } from "react-service-container";
26 | import Greeter from "./greeter";
27 | import Greeting from "./Greeting";
28 |
29 | export default function App() {
30 | return (
31 |
32 |
33 |
34 | );
35 | }
36 | ```
37 |
38 | and use it within your components:
39 |
40 | ```jsx
41 | // Greeting.js
42 | import React from "react";
43 | import { useService } from "react-service-container";
44 | import Greeter from "./greeter";
45 |
46 | export default function Greeting() {
47 | const greeter = useService(Greeter);
48 | return
{greeter.greet()}
;
49 | }
50 | ```
51 |
52 | Testing your components is a breeze:
53 |
54 | ```jsx
55 | import React from "react";
56 | import { render } from "@testing-library/react";
57 | import "@testing-library/jest-dom/extend-expect";
58 | import Greeter from "./greeter";
59 | import Greeting from "./greeting";
60 |
61 | // Greeting.spec.js
62 | test("renders a greeting", () => {
63 | const fakeGreet = jest.fn();
64 | fakeGreet.mockReturnValue("expected greeting");
65 |
66 | const { asFragment } = render(
67 |
70 |
71 |
72 | );
73 |
74 | expect(asFragment()).toHaveTextContent("expected greeting");
75 | });
76 | ```
77 |
78 | [View a working example of the above code](https://codesandbox.io/s/simple-example-00drq)
79 |
80 | The library is based off of the [Service Locator](https://martinfowler.com/articles/injection.html#UsingAServiceLocator) pattern, and the API is inspired by [apollo-client](https://github.com/apollographql/apollo-client).
81 |
82 | ### Features
83 |
84 | - [x] Dead simple to use. No annotations, reflect-metadata, etc. needed
85 | - [x] Idiomatically React. First-class support for hooks and components. Includes react-style descriptive error messages.
86 | - [x] Supports hierarchal containers for lazy loading and code splitting.
87 | - [x] First-class TypeScript support
88 | - [x] Fully tested with 100% code coverage
89 |
90 | Unlike similar libraries, this is **not** a dependency injection library. The sole purpose of this library
91 | is to provide components with services.
92 |
93 | ### Motivation
94 |
95 | When developing React applications, I find that using [Context](https://reactjs.org/docs/context.html) together with [Custom Hooks](https://reactjs.org/docs/hooks-custom.html) provides a really clean and powerful way to inject services into components. For example, if I have a `Greeter` service, such as I used in the example above,
96 | I could write something like this:
97 |
98 | ```jsx
99 | // greeter.js
100 | import { createContext, useContext } from "react";
101 |
102 | export default class Greeter {
103 | greet() {
104 | // ...
105 | }
106 | }
107 |
108 | export const GreeterContext = createContext(null);
109 |
110 | export function useGreeter() {
111 | const instance = useContext(Greeter);
112 | if (!instance) {
113 | throw new Error(`[useGreeter] Greeter was never provided`);
114 | }
115 | return instance;
116 | }
117 | ```
118 |
119 | Then, in my application code I can provide Greeter at runtime, again as shown above:
120 |
121 | ```jsx
122 | // App.js
123 | import React from "react";
124 | import Greeter, { GreeterContext } from "./greeter";
125 | import Greeting from "./Greeting";
126 |
127 | const greeter = new Greeter();
128 | export default function App() {
129 | return (
130 |
131 |
132 |
133 | );
134 | }
135 | ```
136 |
137 | > **NOTE**: In a real app, `Greeting` may be nested somewhere deep down in the component tree, while in this
138 | > example it may look like it would just be easier to pass it as a prop, in a real application you'd have to pass
139 | > it down an entire component tree, making this method more appealing (to me at least).
140 |
141 | Finally, I could use my custom hook in my `Greeting` component:
142 |
143 | ```jsx
144 | // Greeting.js
145 | import React from "react";
146 | import { useGreeter } from "./greeter";
147 |
148 | export default function Greeter() {
149 | const greeter = useGreeter();
150 | return
{greeter.greet()}
;
151 | }
152 | ```
153 |
154 | This not only makes it super easy for components to consume services, but once I started using it, I realized I also preferred providing service mocks in tests explicitly vs. using Jest's
155 | [module mocking](https://jestjs.io/docs/en/mock-functions#mocking-modules). I found that explicitly specifying the services my components rely upon made me less likely to
156 | mock out implementation details and ensure I drew clear boundaries around separation of concern.
157 |
158 | > I also prefer to encapsulate services as classes (call me old-school I guess?), and found Jest's ES6 class mocking to be a bit difficult. This of course is just my personal opinion :sweat_smile:
159 |
160 | However, once I started doing this with multiple services, e.g. `FooService`, `BarService`, `BazService`, I started to get into this slippery slope where not only was I writing a ton of boilerplate code for every service, but my code started looking more diagonal vs. vertical when declaring services.
161 |
162 | ```jsx
163 | import FooService, { FooContext } from "./fooService";
164 | import BarService, { BarContext } from "./barService";
165 | import BazService, { BazContext } from "./bazService";
166 |
167 | const foo = new Foo();
168 | const bar = new Bar();
169 | const baz = new Baz();
170 | export default function App() {
171 |
172 |
173 | {/* ... */}
174 |
175 | ;
176 | }
177 | ```
178 |
179 | I wanted a way to generalize the concept of providing services via contexts and hooks in an easy and intuitive manner, and took inspiration from [Angular's dependency injection system](https://angular.io/guide/dependency-injection) to do so (but without the complexity that true DI comes with). This turned out to work well for my use cases, and hence `react-service-container` was born.
180 |
181 | With `react-service-container`, the above becomes:
182 |
183 | ```jsx
184 | import FooService from "./fooService";
185 | import BarService from "./barService";
186 | import BazService from "./bazService";
187 | import { ServiceContainer } from "react-service-container";
188 |
189 | export default function App() {
190 |
191 | {/* ^_^ */}
192 | ;
193 | }
194 | ```
195 |
196 | Not to mention no more `Context` / hook definition boilerplate in your services.
197 |
198 | 
199 |
200 | ## Installation
201 |
202 | ```
203 | npm i -S react-service-container
204 | ```
205 |
206 | ### UMD Builds
207 |
208 | UMD builds can be found in the npm package's `umd/` folder, containing both development (`react-service-container.js`)
209 | and production (`react-service-container.min.js`) builds. Source maps are included in the folder.
210 |
211 | If you'd like to include react-service-container using a `
216 | ```
217 |
218 | ## Usage
219 |
220 | ### Providing services
221 |
222 | In order to use `react-service-container`, you must create a top-level `ServiceContainer` component, and pass it
223 | a list of **providers** via its `providers` prop that tell `react-service-container` what services are available,
224 | and how to constructor them.
225 |
226 | ```jsx
227 | import Greeter from "./greeter";
228 | import ApiClient from "./apiClient";
229 |
230 | ReactDOM.render(
231 |
240 |
241 |
242 | );
243 | ```
244 |
245 | You can then use the `useService()` hook within your components in order to make use of a service.
246 |
247 | **NOTE**: Each service is only instantiated _once_, the first time `useService()` is called.
248 |
249 | #### The Provider API
250 |
251 | > If you're familiar with [Angular's DI providers](https://angular.io/guide/dependency-injection-providers), you're familiar with ours. The API is pretty much the same.
252 |
253 | Providers come in two forms:
254 |
255 | - An object with a `provide` key whose value is the **service token** you wish to use to represent the service provided, and an additional key the options of which are described below.
256 | - A `Function` object you pass as a shorthand for `{provide: Function, useClass: Function}`
257 |
258 | The providers you can use are listed below in the code example:
259 |
260 | ```js
261 | class MyService {}
262 |
263 | const providers = [
264 | MyService, // Class shorthand
265 | { provide: MyService, useClass: MyService }, // Equivalent to the above
266 | { provide: MyService, useValue: new MyService() }, // Provide a concrete value to be used
267 | { provide: MyService, useFactory: () => new MyService() }, // Provide a factory function, useful for configuring the service
268 | ];
269 | ```
270 |
271 | You can also alias dependencies via `useExisting`
272 |
273 | ```js
274 | class NewService {}
275 |
276 | const providers = [MyService, { provide: NewService, useExisting: MyService }];
277 | ```
278 |
279 | This is useful for gradually deprecating APIs.
280 |
281 | See the tests in this repo for example of using each.
282 |
283 | ### Using Hierarchal Containers
284 |
285 | `react-service-container` fully supports hierarchal `` components. When ``
286 | components are nested within one another, `useService()` calls act exactly like variable lookups: starting from the inner-most to the outer-most service container, the first service token found will be used.
287 |
288 | Using hierarchal service containers not only lets you keep different parts of your codebase cleanly separated, but
289 | it allows for [lazy loading](https://reactjs.org/docs/code-splitting.html) of services at the time in which you need it.
290 |
291 | Say you have an app which shows a list of TODOs, as well as a settings page. You lazily load the page since users tend to navigate to either one page or the other, most likely TODOs. Chances are that the services
292 | for the TODOs page might be different than those used for the settings page. However, some, such as getting
293 | information about the current user, might be shared across the entire application. Using hierarchal service containers
294 | allows the top-level application to contain shared modules, while lazily loaded feature modules can configure their services at load time.
295 |
296 | ```jsx
297 | // src/todos/index.js
298 |
299 | import React from "react";
300 | import { ServiceContainer } from "react-service-container";
301 | import TodosService from "./todosService";
302 |
303 | export default function Todos() {
304 | return (
305 |
306 | {/* Render TODOs */}
307 |
308 | );
309 | }
310 | ```
311 |
312 | ```jsx
313 | // src/settings/index.js
314 |
315 | import React from "react";
316 | import { ServiceContainer } from "react-service-container";
317 | import SettingsService from "./settingsService";
318 |
319 | export default function Settings() {
320 | return (
321 |
322 | {/* Render settings */}
323 |
324 | );
325 | }
326 | ```
327 |
328 | ```js
329 | // src/App.js
330 |
331 | import React, { Suspense, lazy } from "react";
332 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
333 | import { ServiceContainer } from "react-service-container";
334 |
335 | // Common dependency
336 | import UserService from "./userService";
337 | // Loading indicator
338 | import Loading from "./components/Loading";
339 |
340 | // Lazily loaded components
341 | const Todos = lazy(() => import("./todos"));
342 | const Settings = lazy(() => import("./settings"));
343 |
344 | export default function App() {
345 | return (
346 |
347 |
348 | }>
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 | );
360 | }
361 | ```
362 |
363 | Now any components/hooks under `Todos` or `Settings` can call `useService(UserService)`, but only components/hooks under `Todos` can
364 | call `useService(TodosService)` and same for `Settings` and `useService(SettingsService)`;
365 |
366 | ### Wrapping non-class dependencies
367 |
368 | Provide is _simple_. `provide` can be any JS object. Theoretically `{provide: 'hello!', useValue: {...}}` would work.
369 | If you want to inject something that's not a class, try using [Symbols](http://mdn.io/Symbol).
370 |
371 | ```jsx
372 | /** config.js */
373 |
374 | export const config = {
375 | ...,
376 | };
377 |
378 | // NOTE: A string would work fine as well, if you wanted to be simpler. My preference
379 | // is to go for Symbols since they're completely unambiguous.
380 | export const CONFIG = Symbol.for("config");
381 |
382 | /** App.js */
383 |
384 | import {CONFIG, config} from "./config";
385 |
386 | function App() {
387 | return {...}
388 | }
389 | ```
390 |
391 | ### Usage within class components
392 |
393 | Service containers can be easily used without hooks in class components as well. Simply set
394 | the component's `contextType` property to `ServiceContainerContext` and use `this.context.get()`
395 | inside the render method or anywhere else that's needed.
396 |
397 | ```jsx
398 | /* Greeting.js */
399 |
400 | import React from "react";
401 | import { ServiceContainerContext } from "react-service-container";
402 | import Greeter from "./greeter";
403 |
404 | class MyComponent extends React.Component {
405 | static contextType = ServiceContainerContext;
406 |
407 | render() {
408 | const greeter = this.context.get(Greeter);
409 | return
{greeter.greet()}
;
410 | }
411 | }
412 | ```
413 |
414 | ### Usage with TypeScript
415 |
416 | `react-service-container` is written in TypeScript, and comes with first-class support for it. When using `Function`
417 | objects, such as class constructors, with `useService()`, it is properly typed as an instance of that constructor.
418 |
419 | Let's take the example from the introduction and rewrite it in TypeScript.
420 |
421 | ```ts
422 | // greeter.ts
423 |
424 | export default class Greeter {
425 | greet(): string {
426 | return "👋 Hello there!";
427 | }
428 | }
429 | ```
430 |
431 | ```tsx
432 | // App.tsx
433 | import React from "react";
434 | import { ServiceContainer } from "react-service-container";
435 | import Greeter from "./greeter";
436 | import Greeting from "./Greeting";
437 |
438 | export default function App() {
439 | return (
440 |
441 |
442 |
443 | );
444 | }
445 | ```
446 |
447 | ```tsx
448 | // Greeting.tsx
449 | import React from "react";
450 | import { useService } from "react-service-container";
451 | import Greeter from "./greeter";
452 |
453 | export default function Greeting() {
454 | const greeter = useService(Greeter);
455 | return
{greeter.greet()}
;
456 | }
457 | ```
458 |
459 | In the above component, `greeter` is correctly typed to `Greeter`, ensuring type correctness and consistency.
460 |
461 | #### Non-class services
462 |
463 | What about the `config` example earlier?
464 |
465 | ```tsx
466 | /** config.ts */
467 |
468 | export interface Config {/* ... */}
469 |
470 | export const config: Config = {
471 | ...,
472 | };
473 |
474 | export const CONFIG = Symbol.for("config");
475 |
476 | /** App.tsx */
477 |
478 | import {CONFIG, config} from "./config";
479 |
480 | function App() {
481 | return {...}
482 | }
483 | ```
484 |
485 | Here's how we might use that in a component:
486 |
487 | ```tsx
488 | // Component.tsx
489 | import { useService } from "react-service-container";
490 | import { CONFIG } from "./config";
491 |
492 | export default function Component() {
493 | const config = useService(CONFIG);
494 | // render component
495 | }
496 | ```
497 |
498 | Here, `config` is typed as `any`. This is because based on the given Symbol, TypeScript does not _statically_ know
499 | what type the value associated with that symbol is; the symbol could represent any type.
500 |
501 | However, because it's cast to `any`, we can easily typecast the result
502 |
503 | ```tsx
504 | import { useService } from "react-service-container";
505 | import { CONFIG, Config } from "./config";
506 |
507 | export default function Component() {
508 | const config = useService(CONFIG);
509 | // render component
510 | }
511 | const config = useService(CONFIG) as Config;
512 | ```
513 |
514 | This is still less than ideal, since the TypeCasting is ugly and can be repetitive. Here is my preferred approach:
515 |
516 | ```tsx
517 | // config.ts
518 | import {useService} from 'react-service-container';
519 |
520 | export interface Config {/* ... */}
521 |
522 | export const config: Config = {
523 | ...,
524 | };
525 |
526 | const configToken = Symbol.for("config");
527 |
528 | export const CONFIG_PROVIDER = {
529 | provide: configToken,
530 | useValue: config
531 | };
532 |
533 | export const useConfig = () => useService(configToken) as Config;
534 | ```
535 |
536 | By providing a custom `useConfig` hook, and defining the provider within the component, it dramatically reduces the
537 | error surface and repetition of doing manual type-checking, and allows you to abstract away the service token for the config itself.
538 |
539 | ```tsx
540 | // App.tsx
541 | import {ServiceContainer} from 'react-service-container';
542 | import {CONFIG_PROVIDER} from './config';
543 |
544 | function App() {
545 | return {...}
546 | }
547 |
548 | // Component.tsx
549 |
550 | import {useConfig} from './config';
551 |
552 | export default function Component() {
553 | const config = useConfig();
554 | // render component using config, which is now correctly typed.
555 | }
556 | ```
557 |
558 | # License
559 |
560 | MIT
561 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | - [x] Typescript
2 | - [x] Tidy up README
3 | - [x] TypeScript usage / tips
4 | - [x] Rewrite each section
5 | - [x] CI Setup (linting / testing / etc)
6 | - [x] Distribution and publishing
7 | - Source maps, and minification
8 | - [-] Examples (P2)
9 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Still a work in progress. Coming soon!
2 |
--------------------------------------------------------------------------------
/examples/hierarchal-lazy-loading/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/hierarchal-lazy-loading/index.jsx:
--------------------------------------------------------------------------------
1 | import "regenerator-runtime/runtime";
2 |
3 | import React, { useState } from "react";
4 | import ReactDOM from "react-dom";
5 | import { ServiceContainer, useService } from "./lib";
6 | import GithubDAO from "./lib/githubDAO";
7 |
8 | ReactDOM.render(, document.getElementById("root"));
9 |
10 | function App() {
11 | return (
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | function Repos() {
19 | const [loading, setLoading] = useState(false);
20 | const [error, setError] = useState(null);
21 | const [repos, setRepos] = useState(null);
22 | const githubDAO = useService(GithubDAO);
23 |
24 | const loadRepos = async () => {
25 | setLoading(true);
26 | try {
27 | const loadedRepos = await githubDAO.listRepos();
28 | setRepos(loadedRepos);
29 | setError(null);
30 | } catch (err) {
31 | setError(err);
32 | } finally {
33 | setLoading(false);
34 | }
35 | };
36 |
37 | return (
38 |
39 |
40 | {(() => {
41 | if (loading) {
42 | return