![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
@martin_hotell/rea-di
Advanced tools
Dependency injection for React done right. Hierarchical injection on both component and service layer powered by injection-js (Angular DI framework)
Dependency injection for React done right. Hierarchical injection on both component and service layer powered by injection-js (Angular DI framework) 🖖
yarn add @martin_hotell/rea-di
# install peer dependencies
yarn add react injection-js tslib
# install Reflect API polyfill
yarn add core-js
Note:
You need a polyfill for the Reflect API. You can use:
Also for TypeScript you will need to enable
experimentalDecorators
andemitDecoratorMetadata
flags within yourtsconfig.json
Create some services that need other services via DI framework ( Service === ES2015 Class )
// app/services.ts
import 'core-js/es7/reflect';
import { Injectable } from 'injection-js';
@Injectable()
export class Logger {
log(...args:any[]){...}
}
@Injectable()
export class HttpClient {}
@Injectable()
export class UserService {
// constructor Injection
constructor(private httpClient: HttpClient, private logger: Logger) {}
getUsers(): Promise<User[]> {
this.logger.log('get users fetch started')
return this.httpClient.get('api/users')
}
}
// User Model
export class User {
constructor(public id:string, public email: string, public age: number){}
}
Now you'll need to register those services within your component tree via Provider
component that will create new ChildInjector
.
Our DI is hierarchical and only source of truth of this hierarchy is our React Component tree !
So our app Component/DI tree will look like following:
[RootInjector]
|
V
[ChildInjector(<App Component/>)]
- registered providers:
- Logger
- HttpClient
- UserService
|
V
<UserModuleComponent />
|
V
<Inject(UserService) from [ChildInjector] />
// UserService instance will be retrieved from closest Injector, in our case 👉 ChildInjector(App Component)
|
V
<Users props={userService instance from DI framework}>
|
V
<UserList props={users}>
And here is implementation:
// main.ts
import { createElement } from 'react'
import { render } from 'react-dom'
import { App } from './app/app'
boot()
function boot() {
const mountPoint = document.getElementById('app')
render(createElement(App), mountPoint)
}
// app/app.tsx
import React, { Component } from 'react'
import { Provider } from 'rea-di'
import { Logger, HttpClient, UserService } from './services'
import { UserModule } from './user.module'
class App extends Component {
render() {
return (
// We are registering or rootInjector with following services available for whole tree
<Provider provide={[Logger, HttpClient, UserService]}>
<UserModule />
</Provider>
)
}
}
With our injector created, we can now inject our services instances anywhere within the tree.
Also this is the biggest difference and improvement in comparison with Angular. In Angular every provider is registered as global singleton ( if you don't lazy load a module or register it within @Component), With react new ChildInjector will be created anytime you use , so you don't have to be afraid of Services leaking to the root 👌
// app/user.module.tsx
import React, { Component } from 'react'
import { Inject } from 'rea-di'
import { UserService } from './services'
import { Users } from './users'
class UserModule extends Component {
render() {
return (
// { userService: UserService } is our provider map shape which we wanna get within children function
<Inject provide={{ userService: UserService }}>
{/* old good React props Injection, no artificial syntax, just plain old React 👌 */}
{({ userService }) => <Users service={userService} />}
</Inject>
)
}
}
// app/users.tsx
import React, { Component } from 'react'
import { UserService } from './services'
import { UserList } from './user-list'
type Props = {
service: UserService
}
type State = Readonly<typeof initialState>
const initialState = {
users: null as User[],
}
class Users extends Component<Props, State> {
readonly state: State = initialState
render() {
const { users } = this.state
return <div>{users ? 'Loading users...' : <UserList users={users} />}</div>
}
componentDidMount() {
// here we got our UserService instance from the closest injector ( in our case we registered only one), with appropriately resolved Logger and HttpClient services
this.props.service.getUsers().then((result) => {
this.setState({ users: result })
})
}
}
And that's it !
rea-di API is very tiny 👌.
There are 2 components for registering and injecting services and 2 HoC components which just leverage former under the hood (if that's your preferred way of composition).
<Provider provide={[ServiceOne]}>...your tree...</Provider>
withProvider({provide: [ServiceOne]})(MyParentComponent)
<Inject providers={{serviceOne:ServiceOne}}>{({serviceOne})=>...}</Inject>
withInjectables({serviceOne:ServiceOne})(MyComponentWithInjectables)
WithState<T>
abstract class which implements setState
on your service class. If you wanna handle state within your service you need to extend from this Base class and implement state
, exactly like you would with React.Component
Go checkout examples !
For developers with Angular background, storing state within Service is a must have. While that makes sense in Angular ( because handling state within Angular component is a mess ) in React this abstraction isn't needed that much as React component state is mostly sufficient for that purpose.
With react-di
, you can handle state on service layer although we encourage you to handle state internally in Component.state
or via some store state management library ( like Redux ).
For those familiar with
Unstated
, withrea-di
, you got all unstated library power at your disposal within service layer and much more 🌻.
Ok let's look at our previous example. We handle users array state within Users
Component. We can make our UserService
state aware and make it handle our state and with that remove any state from our components.
// app/services.ts
import { WithState } from 'rea-di'
// (1) we define State type and initialState which needs to be implemented when we extend WithState
type State = typeof Readonly<initialState>
const initialState = {
users: null as User[] | null,
}
@Injectable()
// (2) WithState<T> is a generic base class which provides `protected setState()` method and forces you to implement state within your service
export class UserService extends WithState<State> {
// constructor Injection
constructor(private httpClient: HttpClient, private logger: Logger) {
// (3) we need to call super() as we are extending BaseClass
super()
}
// (4) we implement our service state
readonly state: State = initialState
getUsers(): Promise<User[]> {
this.logger.log('get users fetch started')
return this.httpClient.get('api/users').then((response)=>{
// (5) when http finishes, we update our service state.
// This state will work exactly like React state and will re-render components where it's used
this.setState(()=>({users:response}))
})
}
}
With that implemented, we can update our Users
component ( remove state handling from it )
// app/users.tsx
type Props = {
service: UserService
}
class Users extends Component<Props> {
render() {
const { service } = this.props
return (
<div>
{service.state.users ? (
'Loading users...'
) : (
<UserList users={service.state.users} />
)}
</div>
)
}
componentDidMount() {
// we only trigger HTTP call via our injected service. State will be handled and updated internally in that service
this.props.service.getUsers()
}
}
Testing belongs to one of the main areas where DI framework shines!
How to test our components with rea-di ?
You just provide mocks of your services for both unit and integration tests and you're good to go 👌. Old good React ❤️
import { Provide } from 'rea-di'
const DATA: Users[] = [
{
/* ... */
},
{
/* ... */
},
]
class UserServiceMock extends UserService {
getUsers = jest.fn(() => this.setState(() => ({ users: DATA })))
}
describe('<Users/> Unit Test', () => {
it('should fetch users and render them', () => {
const service = new UserServiceMock()
const wrapper = mount(<Users service={service} />)
expect(service.getUsers).toHaveBeenCalled()
expect(service.state).toEqual({ users: DATA })
expect(wrapper.find(UserList)).toBe(true)
})
})
describe('<UsersModule/> Integration Test', () => {
it('should fetch users and render them', () => {
const wrapper = mount(
// we create new ChildInjector with same token, just changing the Implementation that's gonna be instantiated ;)
<Provider provide={[{ provide: UserService, useClass: UserServiceMock }]}>
<UserModule />
</Provider>
)
expect(service.getUsers).toHaveBeenCalled()
expect(service.state).toEqual({ users: DATA })
expect(wrapper.find(UserList)).toBe(true)
})
})
Execute yarn release
which will handle following tasks:
releases are handled by awesome standard-version
1.1.2
to 1.1.2-0
:yarn release --prerelease
1.1.2
to 1.1.2-alpha.0
:yarn release --prerelease alpha
1.1.2
to 1.1.2-beta.0
:yarn release --prerelease beta
See what commands would be run, without committing to git or updating files
yarn release --dry-run
yarn pack
OR yarn release:preflight
which will create a tarball with everything that would get published to NPMTest are written and run via Jest 💪
yarn test
# OR
yarn test:watch
Style guides are enforced by robots, I meant prettier and tslint of course 🤖 , so they'll let you know if you screwed something, but most of the time, they'll autofix things for you. Magic right ?
#Format and fix lint errors
yarn ts:style:fix
yarn docs
WIP: something done
( if you do this please squash your work when you're done with proper commit message so standard-version can create Changelog and bump version of your library appropriately )yarn commit
- will invoke commitizen CLI
MIT as always
FAQs
Dependency injection for React done right. Hierarchical injection on both component and service layer powered by injection-js (Angular DI framework)
The npm package @martin_hotell/rea-di receives a total of 1 weekly downloads. As such, @martin_hotell/rea-di popularity was classified as not popular.
We found that @martin_hotell/rea-di demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.