npm.io
2.13.1 • Published 5d ago

@mic-rexjs/usecases-react

Licence
MIT
Version
2.13.1
Deps
3
Size
315 kB
Vulns
0
Weekly
468

Description

React-based solution for use usecases of Clean Architecture.

Install

$ npm install --save @mic-rexjs/usecases-react
# -
$ yarn add --dev @mic-rexjs/usecases-react

Usage with Non-Entity Mode

// a.ts
import { Reducers } from '@mic-rexjs/usecases';

type MathReducers = Reducers<{
  add(value1: number, value2: number): number;

  subtraction(value1: number, value2: number): number;
}>;

const mathUseCase = (): MathReducers => {
  const add = (value1: number, value2: number): number => {
    return value1 + value2;
  };

  const subtraction = (value1: number, value2: number): number => {
    return value1 - value2;
  };

  return { add, subtraction };
};

// b.tsx
const MyComponent = (): React.ReactElement => {
	const { add, subtraction } = useUseCase(mathUseCase);

	// you can use these reducers any where, and they will be never updated.
	add(1, 2);
	subtraction(5, 3);

	// ...
};

Useage with Entity Mode

// a.ts
import {
	EntityGenerator,
	EntityReducers
} from '@mic-rexjs/usecases';

interface File {
  path: string;
  content: string;
}

interface FileUseCaseOptions {
  maxContentLength?: number;
}

// All reducers should provide the first argument with an entity type T, such as `file: T`.
type FileReducers<T extends File> = EntityReducers<
  T,
  {
    writeFile(entity: T, content: string): EntityGenerator<T, string>;
    isTxt(entity: T): boolean;
  },
>;

const fileUseCase = <T extends File>({ maxContentLength = 2000 }: FileUseCaseOptions = {}): FileReducers<T> => {
  const entityReducers = entityUseCase<T>();

  const writeFile = function* (entity: T, content: string): EntityGenerator<T, string> {
    const { content: oldContent } = entity;
    const newContent = oldContent + content;

    if (newContent.length > maxContentLength) {
      throw 'max length error';
    }

    // set new entity by yield expression
    yield {
      ...entity,
      content: newContent,
    };

    // return the new content
    return newContent;
  };

  const isTxt = (entity: T): boolean => {
    const { path } = entity;

    return path.endsWith('.txt');
  };

  return { ...entityReducers, writeFile, isTxt };
};

// b.tsx
import ReactDOM from 'react-dom';
import { useUseCase } from '@mic-rexjs/usecases-react';

const ParentComponent = ({ children }: React.PropsWithChildren): React.ReactElement => {
  // Pass a default entity to initialize usecase, this usecase must be unique, just like `react context`.
  const [entity, reducers, Provider] = useUseCase({ path: '', content: '' }, fileUseCase);
  const { path } = entity;
  const { writeFile } = reducers;

  const onClick = (): void => {
    // update entity
    writeFile('hello world');
  };

  return (
    // There's no need `value` property
    <Provider>
      <div>
        <header>file path is: {path}</header>
        <main>{children}</main>
        <footer>
          <button onClick={onClick}>write file</button>
        </footer>
      </div>
    </Provider>
  );
};

const ChildComponent = (): React.ReactElement => {
  /**
   * use a usecase which has initialzed in parent component,
   * just like `useContext()`, but the parameter is a usecase.
   */
  const [entity, reducers] = useUseCase(fileUseCase);
  const { content } = entity;
  const { isTxt, writeFile } = reducers;

  const onClick = (): void => {
    // also, you can update entity at child component
    writeFile('hello China');
  };

	/**
	 * reducers will be updated with entity change,
	 * so, you can add some reducers to deps.
	 */
	const ext = useMemo((): string => {
		return isTxt() ? '.txt' : '.js';
	}, [isTxt]);

  return (
    <div>
      <header>ext: {ext}</header>
      <main>{content}</main>
      <footer>
        <button onClick={onClick}>write file</button>
      </footer>
    </div>
  );
};

ReactDOM.render(
  <ParentComponent>
    <ChildComponent />
  </ParentComponent>,
  document.body
);

Test Demos

Keywords