jhonnymichel / react-hookstore

A state management library for react using the bleeding edge hooks feature

Home Page:https://codesandbox.io/s/r58pqonkop

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add typings to bundle or DefinitelyTyped

TomSwiedler opened this issue · comments

any plans on that? do you accept pr with simple .d.ts?
O wait.. you ahve typings but you're not publishing into npm let me try to make a PR

Thanks @CKGrafico for the contribution and @TomSwiedler for the issue created.

We indeed have typings but I'm not a Typescript guy so I just merged a PR from a contributor, but indeed it makes sense that its also needed in the published package, I honestly didnt realise that.

I'll review and merge your PR, thank you.

I'm not really convinced about the typings for dispatch, this code works but typings says incorrect.

bug

edit: you must pass any payload type...

  const [state, dispatch] = useStore<ExampleStoreState, {}>(ExampleStore.name);

@TomSwiedler please use "```" to share code.
PS: I think TPayload must be any by default

Toms code:

  type StateCallback<TState> = (state: TState) => void;

  type ReducerType<TState, TPayload> = (state: TState, payload: TPayload) => TState;

  type SetStateType<TState> = (state: TState, callback?: StateCallback<TState>) => void;

  type DispatchType<TState, TPayload> = (payload: TPayload, callback?: StateCallback<TState>) => void;

  export type StoreStateHookType<TState> = [TState, SetStateType<TState>];

  export type StoreDispatchHookType<TState, TPayload> = [TState, DispatchType<TState, TPayload>];

  const defaultReducer: ReducerType<any>;

  export interface StoreSpec<TState, TPayload> {
    state: TState;
    reducer: ReducerType<TState, TPayload>;
    setState: SetStateType<TState> | DispatchType<TState, TPayload>;
    setters: StateCallback<TState>[]
  }

  export interface StateStoreInterface<TState> {
    getState(): TState;
    setState: SetStateType<TState>;
  }

  export interface ReducerStoreInterface<TState, TPayload> {
    getState(): TState;
    dispatch: DispatchType<TState, TPayload>;
  }

  export function createStore<TState, TPayload>(name: string, state: TState, reducer: ReducerType<TState, TPayload>): ReducerStoreInterface<TState, TPayload>;

  export function createStore<TState>(name: string, state: TState): StateStoreInterface<TState>;

  export function getStoreByName<TState, TPayload>(name: string):  ReducerStoreInterface<TState, TPayload>;

  export function getStoreByName<TState>(name: string): StateStoreInterface<TState>;

  export function useStore<TState>(identifier: string): StoreStateHookType<TState>;

  export function useStore<TState, TPayload>(identifier: string): StoreDispatchHookType<TState, TPayload>;

  export function useStore<TState>(store: StateStoreInterface<TState>): StoreStateHookType<TState>;

  export function useStore<TState, TPayload>(store: ReducerStoreInterface<TState, TPayload>): StoreDispatchHookType<TState, TPayload>;
}

Tom's code with optional payload type

declare module 'react-hookstore' {
  type StateCallback<TState> = (state: TState) => void;

  type ReducerType<TState, TPayload = any> = (state: TState, payload: TPayload) => TState;

  type SetStateType<TState> = (state: TState, callback?: StateCallback<TState>) => void;

  type DispatchType<TState, TPayload = any> = (payload: TPayload, callback?: StateCallback<TState>) => void;

  export type StoreStateHookType<TState> = [TState, SetStateType<TState>];

  export type StoreDispatchHookType<TState, TPayload = any> = [TState, DispatchType<TState, TPayload>];

  const defaultReducer: ReducerType<any>;

  export interface StoreSpec<TState, TPayload = any> {
    state: TState;
    reducer: ReducerType<TState, TPayload>;
    setState: SetStateType<TState> | DispatchType<TState, TPayload>;
    setters: StateCallback<TState>[]
  }

  export interface StateStoreInterface<TState> {
    getState(): TState;
    setState: SetStateType<TState>;
  }

  export interface ReducerStoreInterface<TState, TPayload = any> {
    getState(): TState;
    dispatch: DispatchType<TState, TPayload>;
  }

  export function createStore<TState, TPayload = any>(name: string, state: TState, reducer: ReducerType<TState, TPayload>): ReducerStoreInterface<TState, TPayload>;

  export function createStore<TState>(name: string, state: TState): StateStoreInterface<TState>;

  export function getStoreByName<TState, TPayload>(name: string):  ReducerStoreInterface<TState, TPayload>;

  export function getStoreByName<TState>(name: string): StateStoreInterface<TState>;

  export function useStore<TState>(identifier: string): StoreStateHookType<TState>;

  export function useStore<TState, TPayload = any>(identifier: string): StoreDispatchHookType<TState, TPayload>;

  export function useStore<TState>(store: StateStoreInterface<TState>): StoreStateHookType<TState>;

  export function useStore<TState, TPayload = any>(store: ReducerStoreInterface<TState, TPayload>): StoreDispatchHookType<TState, TPayload>;
}

Yes @TomSwiedler thanks I've just edited my comment haha

That is my last suggestion after @TomSwiedler comment
Have a generic payload for when you don't want to add types to simple actions.

declare module 'react-hookstore' {
  type GenericPayload = any;

  type StateCallback<TState> = (state: TState) => void;

  type ReducerType<TState, TPayload = GenericPayload> = (state: TState, payload: TPayload) => TState;

  type SetStateType<TState> = (state: TState, callback?: StateCallback<TState>) => void;

  type DispatchType<TState, TPayload = GenericPayload> = (payload: TPayload, callback?: StateCallback<TState>) => void;

  export type StoreStateHookType<TState> = [TState, SetStateType<TState>];

  export type StoreDispatchHookType<TState, TPayload = GenericPayload> = [TState, DispatchType<TState, TPayload>];

  const defaultReducer: ReducerType<any>;

  export interface StoreSpec<TState, TPayload = GenericPayload> {
    state: TState;
    reducer: ReducerType<TState, TPayload>;
    setState: SetStateType<TState> | DispatchType<TState, TPayload>;
    setters: StateCallback<TState>[]
  }

  export interface StateStoreInterface<TState> {
    getState(): TState;
    setState: SetStateType<TState>;
  }

  export interface ReducerStoreInterface<TState, TPayload = GenericPayload> {
    getState(): TState;
    dispatch: DispatchType<TState, TPayload>;
  }

  export function createStore<TState, TPayload = GenericPayload>(name: string, state: TState, reducer: ReducerType<TState, TPayload>): ReducerStoreInterface<TState, TPayload>;

  export function createStore<TState>(name: string, state: TState): StateStoreInterface<TState>;

  export function getStoreByName<TState, TPayload>(name: string):  ReducerStoreInterface<TState, TPayload>;

  export function getStoreByName<TState>(name: string): StateStoreInterface<TState>;

  export function useStore<TState>(identifier: string): StoreStateHookType<TState>;

  export function useStore<TState, TPayload = GenericPayload>(identifier: string): StoreDispatchHookType<TState, TPayload>;

  export function useStore<TState>(store: StateStoreInterface<TState>): StoreStateHookType<TState>;

  export function useStore<TState, TPayload = GenericPayload>(store: ReducerStoreInterface<TState, TPayload>): StoreDispatchHookType<TState, TPayload>;
}

Example of use:

const [state, dispatch] = useStore<ExampleStoreState, GenericPayload>(ExampleStore.name);

  useEffect(() => {
    console.log('hooks');
  }, []);


  function onClickText() {
    dispatch({
      type: ExampleStore.Type.ADD_TO_FIRST,
      payload: 10
    });
  }

This can be also another approach

export interface IPayload<T> {
  type: T;
  payload: any;
}

example:

  const [state, dispatch] = useStore<ExampleStoreState, IPayload<ExampleStoreType>>(ExampleStore.name);

Well, since I have no experience on TS, I'll let this decision to you both. I think @CKGrafico last suggestion cover @TomSwiedler case while being simpler, but I really can't tell for sure so, I'll wait on you two agreeing on a final approach

Sorry about the formatting.

Forcing the calls to useStore and CreateStore to fail on type checking when no dispatch action is provided and a dispatch function is used helps to assure the declarations are not different from the implementation. In my case I strongly type the action.type to avoid having the call to dispatch or the reducer labels disagree.

My error again on closing.

mm yes @TomSwiedler but you must try to do it as easy as possible, what do you think about your original code + the interface?

declare module 'react-hookstore' {
  export interface IPayload<Type = any, Action = any> {
    type: Type;
    payload: Action;
  }

  type StateCallback<TState> = (state: TState) => void;

  type ReducerType<TState, TPayload> = (state: TState, payload: TPayload) => TState;

  type SetStateType<TState> = (state: TState, callback?: StateCallback<TState>) => void;

  type DispatchType<TState, TPayload> = (payload: TPayload, callback?: StateCallback<TState>) => void;

  export type StoreStateHookType<TState> = [TState, SetStateType<TState>];

  export type StoreDispatchHookType<TState, TPayload> = [TState, DispatchType<TState, TPayload>];

  const defaultReducer: ReducerType<any>;

  export interface StoreSpec<TState, TPayload> {
    state: TState;
    reducer: ReducerType<TState, TPayload>;
    setState: SetStateType<TState> | DispatchType<TState, TPayload>;
    setters: StateCallback<TState>[]
  }

  export interface StateStoreInterface<TState> {
    getState(): TState;
    setState: SetStateType<TState>;
  }

  export interface ReducerStoreInterface<TState, TPayload> {
    getState(): TState;
    dispatch: DispatchType<TState, TPayload>;
  }

  export function createStore<TState, TPayload>(name: string, state: TState, reducer: ReducerType<TState, TPayload>): ReducerStoreInterface<TState, TPayload>;

  export function createStore<TState>(name: string, state: TState): StateStoreInterface<TState>;

  export function getStoreByName<TState, TPayload>(name: string):  ReducerStoreInterface<TState, TPayload>;

  export function getStoreByName<TState>(name: string): StateStoreInterface<TState>;

  export function useStore<TState>(identifier: string): StoreStateHookType<TState>;

  export function useStore<TState, TPayload>(identifier: string): StoreDispatchHookType<TState, TPayload>;

  export function useStore<TState>(store: StateStoreInterface<TState>): StoreStateHookType<TState>;

  export function useStore<TState, TPayload>(store: ReducerStoreInterface<TState, TPayload>): StoreDispatchHookType<TState, TPayload>;
}

Providing a single interface type with loosely typed properties removes the ability of the type system to work into dispatch and the reducer. This code tends to be deep in the weeds and is prone to typos. If we are just providing the interface as an example and not forcing the typing to the TPayload then users can make their own choice.

We should probably make the payload of the IPayload optional.

Under Redux, the first property of the payload is the selector or action to be taken, the second part is the data. Calling the data portion Action seems confusing.

Ok if you're happy with that seems ok, please can you create or modify my PR with your typings? remember to move the .d.ts file, is in a wrong folder (check #29)

This is how my code looks now and how is going to look :)

module of a store:

import { createStore, ReducerType, useStore } from 'react-hookstore';

const name = 'myexample';

enum Type {
  ADD_TO_FIRST = 'ADD_TO_FIRST',
  ADD_TO_SECOND = 'ADD_TO_SECOND'
}

type Payload = {
  type: Type;
  payload: any;
};

type State = {
  property1: number
  property2: number
};

const state: State = {
  property1: 0,
  property2: 1
};

const reducers: ReducerType<State, Payload> = function(state: State, { type, payload }) {
  switch (type) {
    case Type.ADD_TO_FIRST:
      return { ...state, property1: state.property1 + payload };
    case Type.ADD_TO_SECOND:
      return { ...state, property1: state.property2 + payload };
    default:
      return { ...state };
  }
};

createStore<State, Payload>(name, state, reducers);

export const ExampleStoreType = Type;
export const useExampleStore = () => useStore<State, Payload>(name);

component using it:

export default function () {
  const [state, dispatch] = useExampleStore();

  function onClickText() {
    dispatch({
      type: ExampleStoreType.ADD_TO_FIRST,
      payload: 10
    });
  }
}

Guys, I Merged @CKGrafico PR because I think Itll be easier for anyone else to iterate over its contents since its on master now.

I'm not releasing a new version yet, so don't worry

Well, its been some time, and @CKGrafico thank you so much for your contribution! Ill do a release soon and will close this issue now, We have typings inside the bundle and this is amazing, thank you again @CKGrafico

@CKGrafico hi, could you check if the latest version, 1.3.0, was released correctly with types?

@CKGrafico I will still wait for your feedback, but I tested it and its working AMAZINGLY! Even if I'm not using typecript, vscode provides incredible intellisense out of the typings you created. I am so thankful!

Sorry a lot of work these days :( But if is working in VSCode 99% will work!

Sorry @jhonnymichel I think I've found a missing TPayload

Do yo prefer to reopen this Or I create a new issue? @jhonnymichel

I'll reopen this, I think its easier! thank you for checking

Once I release a new version I'll close this

@CKGrafico
Do you have solutions for typed payload in type Payload (shouldn't it be type Action?)?
In your example you can annotate payload as number. But what if you have more actions?

Example:

import { createStore, ReducerType, useStore } from 'react-hookstore';

const name = 'myexample';

enum Type {
    SET_TITLE = 'SET_TITLE',
    SET_COUNT = 'SET_COUNT'
}

/* TODO: fix any */
type Action = {
    type: Type;
    payload: any;
};

type State = {
    title: string
    count: number
};

const state: State = {
    title: "",
    count: 0,
};

const reducers: ReducerType<State, Action> = function (state: State, action: Action) {
    switch (action.type) {
        case Type.SET_TITLE:
            /* TODO: payload isn't checked by typescript, can put anything  */
            return {...state, title: action.payload};
        case Type.SET_COUNT:
            /* TODO: payload isn't checked by typescript, can put anything  */
            return {...state, count: action.payload};
        default:
            return {...state};
    }
};

const store = createStore<State, Action>(name, state, reducers);

export const ExampleStoreType = Type;
export const useExampleStore = () => useStore<State, Action>(name);


/*
* Export methods for use in async function 
* */

/* TODO: payload isn't checked by typescript, can put anything  */
export const setExampleStoreTitle = (value: string) => store.dispatch<Action>({
    type: Type.SET_TITLE,
    payload: value,
});

/* TODO: payload isn't checked by typescript, can put anything  */
export const setExampleStoreCount = (value: number) => store.dispatch<Action>({
    type: Type.SET_COUNT,
    payload: value,
});