用saga和打字稿连接redux-form

时间:2018-08-22 08:09:36

标签: javascript react-redux redux-form redux-saga

我试图让我了解React,Redux,Saga和Typescript。我设法按照一个示例来连接一个简单的页面,该页面在表格中显示数据。我现在正在尝试创建一个登录表单。我遵循了各种不同的示例。我认为动作,reducer,saga和type部分都可以,但是我真的很难将实际页面连接起来工作。

以下是到目前为止的内容,我正在寻找有关如何使它工作的指南。

打字稿错误 我的index.ts页面上显示以下打字错误:

<form onSubmit={handleSubmit(this.submit)}> //[ts] Expected 5 arguments, but got 1

<button action="submit">LOGIN</button> //[ts] Property 'action' does not exist on type ....

const formed = reduxForm({form: 'signup'})(connected) //[ts] Argument of type 'ComponentClass<Pick<AllProps, "errors" | "handleSubmit ....

问题:

显然有很多,我一直在努力解决这些问题,但没有取得很大的成功,但是我认为这是我需要帮助的地方。 -如何连接表单的onSubmit来调用我的registerRequest操作。 -如何正确连接ReduxForm并在index.tsx末尾导出

页面/注册/index.tsx

import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Dispatch } from 'redux';
import { reduxForm, Field } from 'redux-form'

import { ApplicationState, ConnectedReduxProps } from '../../store'
import { Message, Error } from '../../store/register/types'
import { registerRequest } from '../../store/register/actions'

// Separate state props + dispatch props to their own interfaces.
interface PropsFromState {
  email: string,
  password: string,
  requesting: boolean,
  successful: boolean,
  message: Message[],
  errors: Error[],
}

// We can use `typeof` here to map our dispatch types to the props, like so.
interface PropsFromDispatch {
  registerRequest: typeof registerRequest,
  handleSubmit: typeof PropTypes.func,
  signupRequest: typeof PropTypes.func,
}

// Combine both state + dispatch props - as well as any props we want to pass - in a union type.
type AllProps = PropsFromState & PropsFromDispatch & ConnectedReduxProps

class RegisterIndexPage extends React.Component<AllProps> {
  public componentDidMount() { }

  // Redux Form will call this function with the values of our
  // Form fields `email` and `password` when the form is submitted
  // this will in turn call the action
  submit = (email: string, password: string) => {
    // we could just do signupRequest here with the static proptypes
    // but ESLint doesn't like that very much...
    this.props.registerRequest(email, password)
  }

  public render() {

    // grab what we need from props.  The handleSubmit from ReduxForm
    // and the pieces of state from the global state.
    const { handleSubmit } = this.props

    return (
      <div className="signup">
        {/* Use the Submit handler with our own submit handler*/}
        <form onSubmit={handleSubmit(this.submit)}>
          <h1>Register</h1>
          <label htmlFor="email">Email</label>
          {
          /*
            Our Redux Form Field components that bind email and password
            to our Redux state's form -> register piece of state.
          */}
          <Field
            name="email"
            type="text"
            id="email"
            className="email"
            label="Email"
            component="input"
          />
          <label htmlFor="password">Password</label>
          <Field
            name="password"
            type="password"
            id="password"
            className="password"
            label="Password"
            component="input"
          />
          <button action="submit">LOGIN</button>
        </form>
      </div>

    )
  }
}

// It's usually good practice to only include one context at a time in a connected component.
// Although if necessary, you can always include multiple contexts. Just make sure to
// separate them from each other to prevent prop conflicts.
const mapStateToProps = ({ register }: ApplicationState) => ({
  email: register.email,
  password: register.password,
  requesting: register.requesting,
  successful: register.successful,
  message: register.message,
  error: register.error,
})

// patchToProps is especially useful for constraining our actions to the connected component.
// You can access these via `this.props`.
const mapDispatchToProps = (dispatch: Dispatch, { register }: ApplicationState) => ({
  registerRequest: () => dispatch(registerRequest(register.email, register.password))
})

// Now let's connect our component!
// With redux v4's improved typings, we can finally omit generics here.

const connected = connect(
  mapStateToProps,
  mapDispatchToProps
)(RegisterIndexPage)


// Connect our connected component to Redux Form.  It will namespace
// the form we use in this component as `signup`.
const formed = reduxForm({
  form: 'signup'
})(connected)

// Export our well formed component!
export default formed

register / types.ts

export interface RegisterUser {
  email: string
  password: string
}

export interface Message {
  body: string
  time: Date
}

export interface Error {
  body: string
  time: Date
}

export const enum RegisterActionTypes {
  SIGNUP_REQUESTING = '@@register/SIGNUP_REQUESTING',
  SIGNUP_SUCCESS = '@@register/SIGNUP_SUCCESS',
  SIGNUP_ERROR = '@@register/SIGNUP_ERROR',
}

export interface RegisterState {
  readonly email: string
  readonly password: string
  readonly requesting: boolean,
  readonly successful: boolean,
  readonly message: Message[],
  readonly error: Error[],
}

注册/sagas.ts

import { call, put, takeLatest } from 'redux-saga/effects'
import { RegisterActionTypes } from './types'

import { handleApiErrors } from '../../utils/handleAPIErrors'

const signupUrl = `${process.env.TEST_AUTH_API}/api/Clients`

function signupApi(email: string, password: string) {
  return fetch(signupUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ email, password }),
  })
    .then(handleApiErrors)
    .then(response => response.json())
    .then(json => json)
    .catch((error) => { throw error })
}
function* signupFlow(action: any) {
  try {
    const { email, password } = action
    const response = yield call(signupApi, email, password)
    yield put({ type: RegisterActionTypes.SIGNUP_SUCCESS, response })
  } catch (error) {
    yield put({ type: RegisterActionTypes.SIGNUP_ERROR, error })
  }

}


function* signupWatcher() {
  yield takeLatest(RegisterActionTypes.SIGNUP_REQUESTING, signupFlow)
}

export default signupWatcher

register / reducer.ts

import { Reducer } from 'redux'
import { RegisterState, RegisterActionTypes } from './types'

const initialState: RegisterState = {
  email: "",
  password: "",
  requesting: false,
  successful: false,
  message: [],
  error: [],
}

const reducer: Reducer<RegisterState> = (state = initialState, action) => {
  switch (action.type) {
    case RegisterActionTypes.SIGNUP_REQUESTING: {
      return {
        email: action.response.email,
        password: action.response.password,
        requesting: true,
        successful: false,
        message: [{ body: 'Signing up...', time: new Date() }],
        error: [],
      }
    }
    case RegisterActionTypes.SIGNUP_SUCCESS: {
      return {
        email: action.response.email,
        password: action.response.password,
        error: [],
        message: [{
          body: `Successfully created account for ${action.response.email}`,
          time: new Date(),
        }],
        requesting: false,
        successful: true,
      }
    }
    case RegisterActionTypes.SIGNUP_ERROR: {
      return {
        email: action.response.email,
        password: action.response.password,
        error: state.error.concat([{
          body: action.error.toString(),
          time: new Date(),
        }]),
        message: [],
        requesting: false,
        successful: false,
      }
    }
    default: {
      return state
    }
  }
}

export { reducer as registerReducer }

register / actions.ts

import { action } from 'typesafe-actions'
import { RegisterActionTypes } from './types'

export const registerRequest = (email: string, password: string) => action(RegisterActionTypes.SIGNUP_REQUESTING, { email, password })

export default registerRequest

0 个答案:

没有答案