use-reducer-saga
TypeScript icon, indicating that this package has built-in type declarations

0.2.0-rc.2 • Public • Published

use-saga-reducer

Use redux-saga with react reducer hook, with convenience methods for running sagas from components.

import React, { useEffect, useState, useRef } from 'react'
import { useSaga } from '../../../src'
import { takeEvery, select, delay, put } from 'redux-saga/effects'
 
const reducer = (count: number, action: any) => {
  switch(action.type) {
    case 'INCREMENT':
      return count + 1
    case 'DECREMENT':
      return count - 1
    case 'RESET_COUNT':
      return 0
  }
  return count
}
 
const resetAt = (max: number) => function*() {
  yield takeEvery('INCREMENT', function*() {
    const count = (yield select()) as number
    if(count >= max) {
      yield put({type: 'RESET_COUNT'})
    }
  })
}
 
export const Counter = (props: {max: number}) => {
 
  const [count, dispatch, useRun] = useSaga(reducer, 0, resetAt(props.max))
  const [cycle, setCycle] = useState(0)
 
  useRun(function* () {
    yield takeEvery('RESET_COUNT', function*(a) {
      setCycle( cycle +1 )
    })
  }, [cycle])
 
  return (
    <div>
 
      <button onClick={() => dispatch({type: 'INCREMENT'})}>+</button>
      <button onClick={() => dispatch({type: 'DECREMENT'})}>-</button>
 
      <div>counter{count}</div>
      <div>cycle{cycle}</div>
 
    </div>
  )
}

Example with typed store

SimpleComponent.tsx

import React from 'react'
import { reducer, saga, ping, State, ActionEvent } from './store'
import { useSaga } from '../../../src'
import { take, select } from 'redux-saga/effects'
 
const initialState: State = {
  events: []
}
 
const takePings = (until: number) => function* () {
  while(yield take('ping')) {
    const events = (yield select((s) => s.events)) as ActionEvent[]
    const nrOfPings = events.filter(e => e === 'ping').length    
 
    if(nrOfPings === until) 
      return "counting done"
  }
}
 
export default () => {
 
  const [state, dispatch, useRun] = useSaga(
    reducer, 
    initialState, 
    saga
  )
 
  const counted = useRun(takePings(4), [])
 
  return (
    <>
 
      <div style={{marginBottom: 10}}>
        <button onClick={() => dispatch(ping())}>Ping</button>
      </div>
 
      <div style={{marginBottom: 10}}>
        {state.events.map((event, index) => (
          <i key={index}>
            {event} {' '}
          </i>
        ))}
      </div>
 
      {
        state.error &&
          <b style={{color: 'orange'}}>{ state.error }</b>
      }
 
      
      <div style={{marginTop: 10}}>
        {
          counted.loading ?
            <b style={{color: 'orange'}}>counting..</b> :
          counted.value ?
            <b style={{color: 'green'}}>{ counted.value }</b> :
          "" 
        }
      </div>
 
    </>
  )
}

GlobalSagaReducer.tsx

import React from 'react'
import { useSaga } from 'use-saga-reducer'
import { reducer, saga, ping, State } from './store'
 
const initialState: State = {
  events: []
}
 
const sagaContext = createSagaContext(reducer, initialState, saga)
 
export default () => {
  return (
    <sagaContext.Provider>
 
      <Inner />
      <Inner />
      <Inner />
 
    </sagaContext.Provider>
  )
}
 
export const Inner = () => {
 
  const [state, dispatch, useRun] = sagaContext.use()
 
  return (
    <>
 
      <div style={{marginBottom: 10}}>
        <button onClick={() => dispatch(ping())}>Ping</button>
      </div>
 
      <div style={{marginBottom: 10}}>
        {state.events.map((event, index) => (
          <i key={index}>
            {event} {' '}
          </i>
        ))}
      </div>
 
      {
        state.error &&
          <b style={{color: 'orange'}}>{ state.error }</b>
      }
 
    </>
  )
}

store.ts

 
import { take, put, delay, select, SelectEffect } from 'redux-saga/effects'
 
export type Ping = 'ping'
export type Pong = 'pong'
 
export type PingActionType = {
  type: Ping
}
 
export const ping = (): PingActionType => ({
  type: 'ping'
})
 
export type PongActionType = {
  type: Pong
}
 
export const pong = (): PongActionType => ({
  type: 'pong'
})
 
export type ActionType = 
    PingActionType
  | PongActionType
 
export type ActionEvent = 
    Ping
  | Pong
 
export type State = {
  events: ActionEvent[]
  error?: string
}
 
export const reducer = (state: State, action: ActionType): State => {
  switch(action.type) {
 
    case 'ping':
      const isWaitingForPong = state.events[state.events.length - 1] === 'ping'
 
      if(isWaitingForPong)
        return { ...state, error: 'Invariant: trying to ping while ponging!'}
 
      return {
        events: [...state.events, 'ping']
      }
 
    case 'pong':
      const isWaitingForPing = state.events[state.events.length - 1] === 'pong'
 
      if(isWaitingForPing)
        return { ...state, error: 'Invariant: trying to pong while pinging!'}
 
      return {
        events: [...state.events, 'pong']
      }
 
  }
}
 
export const saga = function* saga() {
  while(yield take('ping')) {
    yield delay(800)
    yield put(pong())
  }
}

Package Sidebar

Install

npm i use-reducer-saga

Weekly Downloads

6

Version

0.2.0-rc.2

License

ISC

Unpacked Size

685 kB

Total Files

37

Last publish

Collaborators

  • gorillatron