Skip to content

chhsiao1981/use-thunk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

use-thunk

codecov

A framework easily using useThunk to manage the data-state.

Adopted concept of redux-thunk and redux-duck

src/useThunkReducer.ts is adopted from nathanbuchar/react-hook-thunk-reducer.

use-thunk is with the following additional features:

  1. The development of the thunk modules follows the concept of redux-duck.
  2. Instead of action / reducer, we focus only on thunk, and have the primitive actions/reducers.

Please check docs/00-introduction.md for more information.

Please check demo-use-thunk for a demo to use Thunk.

Breaking Changes

Install

npm install --save @chhsiao1981/use-thunk

Example

Thunk module able to do increment (reducers/increment.ts):

import { init as _init, setData, Thunk, getState, type State as rState, genUUID } from '@chhsiao1981/use-thunk'

export const myClass = 'demo/Increment'

export interface State extends rState {
  count: number
}

export const defaultState: State = {
  count: 0
}

export const init = (): Thunk<State> => {
  const myID = genUUID()
  return async (dispatch, getClassState) => {
    dispatch(_init({myID, state: defaultState}))
  }
}

export const increment = (myID: string): Thunk<State> => {
  return async (dispatch, getClassState) => {
    let classState = getClassState()
    let me = getState(classState, myID)
    if(!me) {
      return
    }

    dispatch(setData(myID, { count: me.count + 1 }))
  }
}

App.tsx:

import { type ThunkModuleToFunc, useThunk, getRootID, getState } from '@chhsiao1981/use-thunk'
import * as DoIncrement from './reducers/increment'

type TDoIncrement = ThunkModuleToFunc(typeof DoIncrement)

type Props = {
}

export default (props: Props) => {
  const [classStateIncrement, doIncrement] = useThunk<DoIncrement.State, TDoIncrement>(DoIncrement, StateType.LOCAL)

  //init
  useEffect(() => {
    doIncrement.init()
  }, [])

  // states
  const incrementID = getRootID(classStateIncrement)
  const increment = getState(classStateIncrement) || DoIncrement.defaultState

  // to render
  return (
    <div>
      <p>count: {increment.count}</p>
      <button onClick={() => doIncrement.increment(incrementID)}>increase</button>
    </div>
  )
}

main.tsx:

import { registerThunk, ThunkContext } from "@chhsiao1981/use-thunk";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import * as DoIncrement from './reducers/increment'
import App from "./App.tsx";

registerThunk(DoIncrement)

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ThunkContext>
      <App />
    </ThunkContext>
  </StrictMode>,
)

Must Included in a Thunk Module

import type { State as rState } from '@chhsiao1981/use-thunk'

// reducer class name.
export const myClass = ""

// state definition of the reducer.
export interface State extends rState {
}

.
.
.

Must Included in a Top-level Component

import { type ThunkModuleToFunc, useThunk, getRootID, getState } from '@chhsiao1981/use-thunk'
import * as DoModule from '../reducers/module'

type TDoModule = ThunkModuleToFunc<typeof DoModule>

const Component = () => {
  const [stateModule, doModule] = useThunk<DoModule.State, TDoModule>(DoModule)

  const moduleID = getRootID(stateModule)
  const theModule = getState(stateModule)

  .
  .
  .
}

Must Included in main.tsx

registerThunk(...)
.
.
.

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ThunkContext>
      <App />
    </ThunkContext>
  </StrictMode>,
)

Normalized State

The general concept of normalized state can be found in Normalizing State Shape with the following features:

  1. ClassState: the state of the class, including the nodes and the root of the class.
  2. NodeState: the state of a node, including the id of the node and the content (state) of the node.
  3. State: the content of the node, represented as a state.

For example, the example in the redux link is represented as:

classStatePost = {
  myClass: 'post',
  doMe: (DispatchedAction<Post>),
  nodes: {
    [uuid-post1] : {
      id: uuid-post1,
      state: {
        author : uuid-user1,
        body : "......",
        comments: [uuid-comment1, uuid-comment2]
      },
    },
    [uuid-post2] : {
      id : uuid-post2,
      state: {
        author : uuid-user2,
        body : "......",
        comments: [uuid-comment3, uuid-comment4, uuid-comment5]
      }
    }
  }
}

and:

classStateComment = {
  myClass: 'comment',
  doMe: (DispatchedAction<Comment>),
  nodes: {
    [uuid-comment1] : {
      id: uuid-comment1,
      state: {
        author : uuid-user2,
        comment : ".....",
      }
    },
    [uuid-comment2] : {
      id : uuid-comment2,
      state: {
        author : uuid-user3,
        comment : ".....",
      }
    },
    [uuid-comment3] : {
      id : uuid-comment3,
      state: {
        author : uuid-user3,
        comment : ".....",
      }
    },
    [uuid-comment4] : {
      id : uuid-comment4,
      state: {
        author : uuid-user1,
        comment : ".....",
      }
    },
    [uuid-comment5] : {
      id : uuid-comment5,
      state: {
        author : uuid-user3,
        comment : ".....",
      }
    }
  }
}

and:

classStateUser = {
  myClass: 'user',
  doMe: (DispatchedAction<User>),
  nodes: {
    [uuid-user1] : {
      id: uuid-user1,
      state: {
        username : "user1",
        name : "User 1",
      }
    },
    [uuid-user2] : {
      id: uuid-user2,
      state: {
        username : "user2",
        name : "User 2",
      }
    },
    [uuid-user3] : {
      id: uuid-user3,
      state: {
        username : "user3",
        name : "User 3",
      }
    }
  }
}

Basic

useThunk(theDo: ThunkModuleFunc): [ClassState, DispatchedAction]

Similar to React.useReducer, but we use useThunk, and we also bind the actions with dispatch (similar concept as mapDispatchToProps).s

return: [ClassState<S>, DispatchedAction<S>]

init({myID, parentID, doParent, state}, myuuidv4?)

initializing the react-object.

setData(myID, data)

set the data to myID.

remove(myID, isFromParent=false)

remove the react-object.

genUUID(myuuidv4?: () => string): string

generate uuid for react-object.

State

getState(state: ClassState, myID?: string): State

Get the state of myID. Get the state of rootID if myID is not present.

getRootID(state: ClassState): string

get the root id.

NodeState

getNode(state: ClassState, myID?: string): NodeState

Get the node of myID. Get the node of rootID if myID is not present.

About

thunk-only reducer.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •