Add better type checking when connecting components

This commit is contained in:
Jerko Steiner 2019-03-20 16:57:41 +05:00
parent 150a02b344
commit f2e44f477c
8 changed files with 46 additions and 33 deletions

View File

@ -1,7 +1,7 @@
// Maybe this won't be necessary after this is merged: // Maybe this won't be necessary after this is merged:
// https://github.com/Microsoft/TypeScript/pull/29478 // https://github.com/Microsoft/TypeScript/pull/29478
export interface IAction<T, ActionType extends string> { export interface IAction<T = any, ActionType extends string = string> {
payload: Promise<T> | T, payload: Promise<T> | T,
type: ActionType type: ActionType
} }

View File

@ -7,7 +7,7 @@ import {Redirect} from '../components/Redirect'
export interface ILoginFormProps { export interface ILoginFormProps {
error?: string error?: string
onSubmit: () => Promise<IUser> onSubmit: () => void
onChange: (name: string, value: string) => void onChange: (name: string, value: string) => void
data: ICredentials data: ICredentials
user?: IUser user?: IUser

View File

@ -1,5 +1,5 @@
import {Connector} from '../redux/Connector' import {Connector} from '../redux/Connector'
import {ICredentials} from '@rondo/common' import {INewUser} from '@rondo/common'
import {ILoginState} from './LoginReducer' import {ILoginState} from './LoginReducer'
import {IStateSelector} from '../redux' import {IStateSelector} from '../redux'
import {LoginActions} from './LoginActions' import {LoginActions} from './LoginActions'
@ -7,9 +7,11 @@ import {RegisterForm} from './RegisterForm'
import {bindActionCreators} from 'redux' import {bindActionCreators} from 'redux'
import {withForm} from './withForm' import {withForm} from './withForm'
const defaultCredentials: ICredentials = { const defaultCredentials: INewUser = {
username: '', username: '',
password: '', password: '',
firstName: '',
lastName: '',
} }
export class RegisterConnector extends Connector<ILoginState> { export class RegisterConnector extends Connector<ILoginState> {

View File

@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import {IAction} from '../actions'
export interface IComponentProps<Data> { export interface IComponentProps<Data> {
onSubmit: () => void onSubmit: () => void
@ -9,7 +10,7 @@ export interface IComponentProps<Data> {
} }
export interface IFormHOCProps<Data> { export interface IFormHOCProps<Data> {
onSubmit: (props: Data) => Promise<void> onSubmit: (props: Data) => IAction<any, any>
// TODO figure out what would happen if the underlying child component // TODO figure out what would happen if the underlying child component
// would have the same required property as the HOC, like onSuccess? // would have the same required property as the HOC, like onSuccess?
onSuccess?: () => void onSuccess?: () => void
@ -33,7 +34,9 @@ export function withForm<Data, Props extends IComponentProps<Data>>(
handleSubmit = async (e: React.FormEvent) => { handleSubmit = async (e: React.FormEvent) => {
const {clearOnSuccess, onSuccess} = this.props const {clearOnSuccess, onSuccess} = this.props
e.preventDefault() e.preventDefault()
await this.props.onSubmit(this.state) const promise = this.props.onSubmit(this.state)
console.log('aaaaaaaaa', promise)
await promise
if (clearOnSuccess) { if (clearOnSuccess) {
this.setState(initialState) this.setState(initialState)
} }

View File

@ -35,9 +35,9 @@ export abstract class Connector<LocalState> {
protected wrap< protected wrap<
State, State,
StateProps, Props,
DispatchProps, StateProps extends Partial<Props>,
Props DispatchProps extends Partial<Props>,
>( >(
getLocalState: IStateSelector<State, LocalState>, getLocalState: IStateSelector<State, LocalState>,
mapStateToProps: (state: LocalState) => StateProps, mapStateToProps: (state: LocalState) => StateProps,

View File

@ -1,25 +1,26 @@
import React from 'react' import React from 'react'
import {IAction} from '../actions'
import {ITeam, ReadonlyRecord} from '@rondo/common' import {ITeam, ReadonlyRecord} from '@rondo/common'
export interface ITeamListProps { export interface ITeamListProps {
teamsById: ReadonlyRecord<number, ITeam>, teamsById: ReadonlyRecord<number, ITeam>,
teamIds: ReadonlyArray<number>, teamIds: ReadonlyArray<number>,
onAddTeam: (params: {name: string}) => Promise<void> onAddTeam: (params: {name: string}) => IAction
onRemoveTeam: (params: {id: number}) => Promise<void> onRemoveTeam: (params: {id: number}) => IAction
onUpdateTeam: (params: {id: number, name: string}) => Promise<void> onUpdateTeam: (params: {id: number, name: string}) => IAction
editTeamId: number editTeamId: number
} }
export interface ITeamProps { export interface ITeamProps {
team: ITeam team: ITeam
editTeamId: number // TODO handle edits via react-router params editTeamId: number // TODO handle edits via react-router params
onRemoveTeam: (params: {id: number}) => Promise<void> onRemoveTeam: (params: {id: number}) => IAction
onUpdateTeam: (params: {id: number, name: string}) => Promise<void> onUpdateTeam: (params: {id: number, name: string}) => IAction
} }
export interface IAddTeamProps { export interface IAddTeamProps {
onAddTeam: (params: {name: string}) => Promise<void> onAddTeam: (params: {name: string}) => IAction
onUpdateTeam: (params: {id: number, name: string}) => Promise<void> onUpdateTeam: (params: {id: number, name: string}) => IAction
team?: ITeam team?: ITeam
} }

View File

@ -1,19 +1,19 @@
import React from 'react' import React from 'react'
import {IAction} from '../actions'
import {ITeam, IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common' import {ITeam, IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common'
import {TeamList} from './TeamList' import {TeamList} from './TeamList'
import {TeamUserList} from './TeamUserList' import {TeamUserList} from './TeamUserList'
// import {Route} from 'react-router-dom'
export interface ITeamManagerProps { export interface ITeamManagerProps {
createTeam: (params: {name: string}) => Promise<void> createTeam: (params: {name: string}) => IAction
updateTeam: (params: {id: number, name: string}) => Promise<void> updateTeam: (params: {id: number, name: string}) => IAction
removeTeam: (params: {id: number}) => Promise<void> removeTeam: (params: {id: number}) => IAction
addUser: (params: {userId: number, teamId: number}) => Promise<void> addUser: (params: {userId: number, teamId: number, roleId: number}) => IAction
removeUser: (params: {userId: number, teamId: number}) => Promise<void> removeUser: (params: {userId: number, teamId: number}) => IAction
fetchMyTeams: () => void fetchMyTeams: () => IAction
fetchUsersInTeam: () => void fetchUsersInTeam: (params: {teamId: number}) => IAction
findUserByEmail: (email: string) => Promise<IUser> findUserByEmail: (email: string) => IAction<IUser | undefined>
teamsById: ReadonlyRecord<number, ITeam> teamsById: ReadonlyRecord<number, ITeam>
teamIds: ReadonlyArray<number> teamIds: ReadonlyArray<number>

View File

@ -1,15 +1,17 @@
import React from 'react' import React from 'react'
import {IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common' import {IUser, IUserInTeam, ReadonlyRecord} from '@rondo/common'
import {IAction} from '../actions'
const EMPTY_ARRAY: ReadonlyArray<string> = [] const EMPTY_ARRAY: ReadonlyArray<string> = []
export interface ITeamUsersProps { export interface ITeamUsersProps {
// fetchMyTeams: () => void, // fetchMyTeams: () => void,
fetchUsersInTeam: (teamId: number) => void fetchUsersInTeam: (params: {teamId: number}) => IAction
findUserByEmail: (email: string) => Promise<IUser> findUserByEmail: (email: string) => IAction
onAddUser: (params: {userId: number, teamId: number}) => Promise<void> onAddUser: (params: {userId: number, teamId: number, roleId: number})
onRemoveUser: (params: {userId: number, teamId: number}) => Promise<void> => IAction<IUserInTeam>
onRemoveUser: (params: {userId: number, teamId: number}) => IAction
teamId: number teamId: number
userKeysByTeamId: ReadonlyRecord<number, ReadonlyArray<string>> userKeysByTeamId: ReadonlyRecord<number, ReadonlyArray<string>>
@ -22,8 +24,12 @@ export interface ITeamUserProps {
} }
export interface IAddUserProps { export interface IAddUserProps {
onAddUser: (params: {userId: number, teamId: number}) => Promise<void> onAddUser: (params: {
onSearchUser: (email: string) => Promise<IUser> userId: number,
teamId: number,
roleId: number,
}) => IAction<IUserInTeam>
onSearchUser: (email: string) => IAction<IUser>
teamId: number teamId: number
} }
@ -70,7 +76,7 @@ export class AddUser extends React.PureComponent<IAddUserProps, IAddUserState> {
event.preventDefault() event.preventDefault()
const {teamId} = this.props const {teamId} = this.props
const {email} = this.state const {email} = this.state
const user = await this.props.onSearchUser(email) const user = await this.props.onSearchUser(email).payload
if (!user) { if (!user) {
// TODO handle this better via 404 status code // TODO handle this better via 404 status code
return return
@ -78,6 +84,7 @@ export class AddUser extends React.PureComponent<IAddUserProps, IAddUserState> {
await this.props.onAddUser({ await this.props.onAddUser({
teamId, teamId,
userId: user.id, userId: user.id,
roleId: 1,
}) })
this.setState({email: '', user: undefined}) this.setState({email: '', user: undefined})
@ -111,7 +118,7 @@ export class TeamUserList extends React.PureComponent<ITeamUsersProps> {
} }
async fetchUsersInTeam(teamId: number) { async fetchUsersInTeam(teamId: number) {
if (teamId) { if (teamId) {
await this.props.fetchUsersInTeam(teamId) await this.props.fetchUsersInTeam({teamId})
} }
} }
render() { render() {