Skip to content

Commit

Permalink
feat: wire decorator for wire storepart to store
Browse files Browse the repository at this point in the history
  • Loading branch information
Amir Hossein Qasemi Moqaddam authored and amirqasemi74 committed Jan 11, 2022
1 parent 8ec30a5 commit b69bd29
Show file tree
Hide file tree
Showing 32 changed files with 736 additions and 545 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class UserStore {
}
```

Then connect it to the component **Tree** by using `connectStore`:
Then connect it to the component **Tree** by using `connect`:

```tsx
// App.tsx
Expand All @@ -58,7 +58,7 @@ function App(props: Props) {
</div>
);
}
export default connectStore(App, UserStore);
export default connect(App, UserStore);
```

And enjoy to use store in child components by `useStore` hook. pass **Store Class** as first parameter:
Expand Down Expand Up @@ -128,7 +128,7 @@ export class UserStore {

## Props in store

To have store parent component props (the component directly connected to store by using `connectStore`) inside store class use `@Props()`:
To have store parent component props (the component directly connected to store by using `connect`) inside store class use `@Props()`:

```ts
// user.store.ts
Expand Down Expand Up @@ -174,7 +174,7 @@ class PostService {
}
```

Injection works fine for **stores**. Injectable can be injected into all stores. Also stores can be injected into other stores but there is one condition. For example, you want to inject `A` store into `B` store so the component which is wrapped with `connectStore(..., A)` must be higher in `B` store parent component. In other words, it works like React `useContext` rule.
Injection works fine for **stores**. Injectable can be injected into all stores. Also stores can be injected into other stores but there is one condition. For example, you want to inject `A` store into `B` store so the component which is wrapped with `connect(..., A)` must be higher in `B` store parent component. In other words, it works like React `useContext` rule.

```ts
@Injectable()
Expand Down
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "@react-store/core",
"version": "0.0.8",
"version": "0.0.9",
"main": "dist/index.js",
"repository": "https://github.com/amirqasemi74/react-store.git",
"repository": "https://github.com/sahabpardaz/react-store.git",
"author": "Amir Hossein Qasemi Moqaddam <[email protected]>",
"license": "MIT",
"dependencies": {
Expand Down Expand Up @@ -32,22 +32,22 @@
"@types/react-dom": "^17.0.11",
"@types/styled-components": "^5.1.19",
"@types/testing-library__jest-dom": "^5.14.2",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"@typescript-eslint/eslint-plugin": "^5.9.0",
"@typescript-eslint/parser": "^5.9.0",
"@zerollup/ts-transform-paths": "^1.7.18",
"babel-jest": "^27.4.5",
"babel-jest": "^27.4.6",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.5.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"jest": "^27.4.5",
"jest": "^27.4.7",
"lint-staged": ">=12",
"prettier": "^2.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-refresh-typescript": "^2.0.3",
"rollup": "^2.62.0",
"rollup": "^2.63.0",
"styled-components": "^5.3.3",
"ts-loader": "^9.2.6",
"ttypescript": "^1.5.13",
Expand Down
4 changes: 2 additions & 2 deletions sampleApp/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import ToDos from "./toDos/ToDos";
import { ToDoStore } from "./toDos/toDo.store";
import { PureToDos } from "./todos-pure/PureToDos";
import { PureToDosProvider } from "./todos-pure/PureToDosProvider";
import { StoreProvider, connectStore, useStore } from "@react-store/core";
import { StoreProvider, connect, useStore } from "@react-store/core";
import React, { useState } from "react";

const App = () => {
Expand All @@ -19,4 +19,4 @@ const App = () => {
);
};

export default connectStore(App, ThemeStore);
export default connect(App, ThemeStore);
10 changes: 7 additions & 3 deletions sampleApp/libs/formValidator.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Effect, StorePart } from "@react-store/core";
import ThemeStore from "sampleApp/theme.store";
import { UserService } from "sampleApp/toDos/services/userService";

@StorePart()
export class FormValidator {
hasAnyError = false;

constructor(private form: any) {}
form?: any;

@Effect<any>("form", true)
constructor(protected theme: ThemeStore, protected userService: UserService) {}

@Effect("form", true)
validate() {
new Promise((res) => res(1)).then(() => {
this.hasAnyError = !this.form.value;
this.hasAnyError = !this.form?.value;
});
}
}
4 changes: 2 additions & 2 deletions sampleApp/toDos/ToDoItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface Props {
itemIndex: number;
}

const ToDoItem: React.FC<Props> = React.memo(({ itemIndex }) => {
const ToDoItem: React.FC<Props> = ({ itemIndex }) => {
const vm = useStore(ToDoStore, ({ todos }) => [todos[itemIndex]]);

return (
Expand All @@ -29,7 +29,7 @@ const ToDoItem: React.FC<Props> = React.memo(({ itemIndex }) => {
</ActionWrapper>
</ToDoItemWrapper>
);
});
};

export default ToDoItem;

Expand Down
16 changes: 14 additions & 2 deletions sampleApp/toDos/base.store.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import ThemeStore from "../theme.store";
import { Effect, Store } from "@react-store/core";
import { AutoWire, Effect, Observable, Store, Wire } from "@react-store/core";
import { FormValidator } from "sampleApp/libs/formValidator";
import { ToDoService } from "sampleApp/toDos/services/todos.service";

@Store()
export class BaseStore {
todo = new ToDo();

@AutoWire()
readonly validator: FormValidator;

constructor(public theme: ThemeStore, public todoService: ToDoService) {}

@Effect<BaseStore>([])
@Effect([])
onMount() {
this.validator.form = this.todo;
console.log("mounted");
}
}

@Observable()
class ToDo {
value = "";
}
4 changes: 2 additions & 2 deletions sampleApp/toDos/services/todos.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UserService } from "./userService";
import { Inject, Injectable, Injector } from "@react-store/core";
import { Injectable, Injector } from "@react-store/core";

@Injectable()
export class ToDoService {
Expand All @@ -12,6 +12,6 @@ export class ToDoService {
}

toDos() {
console.log("Gettig todos for user", this.userService.username);
console.log("Getting todos for user", this.userService.username);
}
}
2 changes: 1 addition & 1 deletion sampleApp/toDos/services/userService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ToDoService } from "./todos.service";
import { Inject, Injectable, Injector } from "@react-store/core";
import { Injectable, Injector } from "@react-store/core";

@Injectable()
export class UserService {
Expand Down
13 changes: 2 additions & 11 deletions sampleApp/toDos/toDo.store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseStore } from "./base.store";
import { Effect, Observable, Props, Store } from "@react-store/core";
import { Effect, Props, Store } from "@react-store/core";
import { ChangeEvent, KeyboardEvent } from "react";
import { FormValidator } from "sampleApp/libs/formValidator";

Expand All @@ -10,13 +10,9 @@ export class ToDoStore extends BaseStore {

todos: ToDoItem[] = [{ id: "123", value: "Job -1", isEditing: false }];

todo = new ToDo();

validator = new FormValidator(this.todo);

d = new Date();

@Effect<ToDoStore>([])
@Effect([])
init() {
for (let i = 0; i < 10; i++) {
this.todos.push({
Expand Down Expand Up @@ -66,11 +62,6 @@ export class ToDoStore extends BaseStore {
}
}

@Observable()
class ToDo {
value = "";
}

interface ToDoItem {
id: string;
value: string;
Expand Down
5 changes: 4 additions & 1 deletion src/container/decorators/inject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { StoreMetadataUtils } from "../../decorators/store";
import { InjectableMetadataUtils } from "./Injectable";
import { StorePartMetadataUtils } from "src/decorators/storePart";

export function Inject(...deps: any[]) {
return function (...args: [Function] | [Function, undefined, number]) {
Expand Down Expand Up @@ -103,6 +104,8 @@ export class InjectMetadataUtils {
const isDecorated =
decoratedWith === "STORE"
? StoreMetadataUtils.is(target)
: decoratedWith === "STORE_PART"
? StorePartMetadataUtils.is(target)
: InjectableMetadataUtils.is(target);

const ownDeps = this.getOwnDependencies(target);
Expand All @@ -126,5 +129,5 @@ interface ConstructorDependency {
injectType: InjectType;
}

type DecoratedWith = "STORE" | "INJECTABLE";
export type DecoratedWith = "STORE" | "STORE_PART" | "INJECTABLE";
type InjectType = "CLASS" | "CLASS_METADATA" | "PARAMETER";
15 changes: 15 additions & 0 deletions src/decorators/autoWire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Wire } from "..";

export function AutoWire(): PropertyDecorator {
return function (target, propertyKey) {
const type = Reflect.getOwnMetadata("design:type", target, propertyKey);
if (!type) {
throw new Error(
`AutoWire for ${
target.constructor.name
}.${propertyKey.toString()} can't detect type. use \`@Wire(...) instead.\'`
);
}
Wire(type)(target, propertyKey);
};
}
12 changes: 5 additions & 7 deletions src/decorators/storePart.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { EnhancedStoreFactory } from "src/react/store/enhancedStoreFactory";
import { Inject } from "..";

export function StorePart(): ClassDecorator {
return function (StoreType: any) {
// TODO: must be removed
const EnhancedStoreType = EnhancedStoreFactory.create(StoreType);
StorePartMetadataUtils.set(EnhancedStoreType);
return EnhancedStoreType;
} as any;
return function (StorePartType: any) {
StorePartMetadataUtils.set(StorePartType);
Inject()(StorePartType);
};
}

export class StorePartMetadataUtils {
Expand Down
40 changes: 40 additions & 0 deletions src/decorators/wire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ClassType } from "src/types";

export function Wire(type: Function): PropertyDecorator {
return function (target, propertyKey) {
WireMetadataUtils.set(target.constructor, propertyKey, type);
};
}

export class WireMetadataUtils {
private static readonly KEY = Symbol();

static set(target: Function, propertyKey: PropertyKey, type: Function) {
const wires = this.getAllOwn(target);
wires.push({ propertyKey, type });
Reflect.defineMetadata(this.KEY, wires, target);
}

static getAllOwn(target: Function): WireDetail[] {
return Reflect.getOwnMetadata(this.KEY, target) || [];
}

static getAll(target: Function): WireDetail[] {
let wires = this.getAllOwn(target);
const parentClass = Reflect.getPrototypeOf(target) as ClassType;
if (parentClass) {
wires = wires.concat(this.getAll(parentClass));
}
return wires;
}

static is(target: Function, propertyKey: PropertyKey) {
const wires = this.getAll(target);
return wires.some(({ propertyKey: _p }) => _p === propertyKey);
}
}

interface WireDetail {
type: Function;
propertyKey: PropertyKey;
}
10 changes: 6 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export { getFromContainer } from "./container/container";
export { Injectable, Scope } from "./container/decorators/Injectable";
export { Injector } from "./container/Injector";
export { useStore } from "./react/hooks/useStore";
export { Store } from "./decorators/store";
export { connect } from "./react/store/connect";
export { StoreProvider } from "./react/store/StoreProvider";
export { Props } from "./decorators/props";
export { UseContext } from "./decorators/useContext";
export { connectStore } from "./react/store/connectStore";
export { StoreProvider } from "./react/store/StoreProvider";
export { Effect } from "./decorators/effect";
export { Store } from "./decorators/store";
export { Inject } from "./container/decorators/inject";
export { StorePart } from "./decorators/storePart";
export { Injector } from "./container/Injector";
export { Action } from "./decorators/action";
export { Observable } from "./decorators/observable";
export { Wire } from "./decorators/wire";
export { AutoWire } from "./decorators/autoWire";
3 changes: 1 addition & 2 deletions src/react/store/StoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ReactApplicationContext } from "../appContext";
import { registerHandlers } from "../handlers/registerHandlers";
import { StoreAdministrator } from "./administrator/storeAdministrator";
import { StoreFactory } from "./storeFactory";
import React, { useMemo } from "react";
Expand Down Expand Up @@ -33,7 +32,7 @@ export const StoreProvider = ({ type, render, props }: Props) => {
return context;
});

const { store, storeAdmin } = StoreFactory.create(type, props);
const storeAdmin = StoreFactory.create(type, props);

const Component = useMemo(() => React.memo(render), []);

Expand Down
File renamed without changes.
Loading

0 comments on commit b69bd29

Please sign in to comment.