Skip to content

Commit

Permalink
app: simple API client based on rxjs added
Browse files Browse the repository at this point in the history
  • Loading branch information
rodmax committed Nov 8, 2019
1 parent dadc58b commit ec399d7
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 29 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ The main purpose of this project is to study web applications development based
the best approach in terms of minimal boilerplate|strong typing|no unneeded abstractions/libs in my opinion
- [ ] Effects/async flow: RxJs(rxjs/observable)
- [ ] Router: router5
- API client (rxjs based?)
- [ ] Base API client helper to be used for every domain API client
- [ ] File uploading
- [ ] Catch unhandled API errors on App level
- React related:
- [x] [React.StrictMode](https://reactjs.org/docs/strict-mode.html)
- [ ] try suspense
Expand Down
20 changes: 20 additions & 0 deletions src/app/modules/github-profile/api/github-profile-api.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fromFetch } from 'rxjs/fetch'
import { GithubUserDto } from './github-profile-api.typings'
import { switchMap } from 'rxjs/operators'
import { throwError, Observable } from 'rxjs'

export const ghProfileApiClient = {
getProfile,
}

function getProfile(username: string): Observable<GithubUserDto> {
return fromFetch(`https://api.github.com/users/${username}`).pipe(
switchMap(resp => {
if (resp.ok) {
return resp.json()
} else {
return throwError(resp)
}
})
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import { AppState } from '../../../core/store'

export const GithubProfileCard: React.FC = () => {
const userDto = useSelector(selectGithubUserDto)
if (!userDto) {
return <h4>Loading...</h4>
}
return (
<>
<h2>Github profile</h2>
<pre>{JSON.stringify(userDto)}</pre>
<h3>{userDto.login}</h3>
<img src={userDto.avatar_url} />
<pre>{JSON.stringify(userDto, null, 4)}</pre>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { GithubUserDto } from '../api/github-profile.api.typings'
import { GithubUserDto } from '../api/github-profile-api.typings'
import { rdxActionCreator } from '../../../shared/redux/redux-tools'

export const ghProfileActions = {
fetchRequested: rdxActionCreator('@ghProfile.fetchRequested').withNoPayload(),
fetchRequested: rdxActionCreator('@ghProfile.fetchRequested').withPayload<{
username: string
}>(),
fetchComplete: rdxActionCreator('@ghProfile.fetchComplete').withPayload<GithubUserDto>(),
fetchError: rdxActionCreator('@ghProfile.fetchError').withPayload<object>(),
} as const
Expand Down
16 changes: 9 additions & 7 deletions src/app/modules/github-profile/state/github-profile.epics.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { Epic, ofType } from 'redux-observable'
import { switchMap, map, startWith } from 'rxjs/operators'
import { of } from 'rxjs'
import { GithubUserDto } from '../api/github-profile.api.typings'
import { ghProfileApiClient } from '../api/github-profile-api.client'
import { GhProfileAnyAction, ghProfileActions } from './github-profile.actions'

const DEFAULT_USERNAME = 'rodmax'

export const ghProfileFetchDataEpic: Epic<GhProfileAnyAction> = action$ => {
return action$.pipe(
ofType(ghProfileActions.fetchRequested.type),
startWith(true), // For debug we load data when app inited
switchMap(() => {
return of({ login: 'rod-max' } as GithubUserDto).pipe(
map(useDto => {
return ghProfileActions.fetchComplete.create(useDto)
return ghProfileApiClient.getProfile(DEFAULT_USERNAME).pipe(
map(userDto => {
return ghProfileActions.fetchComplete.create(userDto)
})
)
})
}),
// Kick start initial loading
startWith(ghProfileActions.fetchRequested.create({ username: DEFAULT_USERNAME }))
)
}
11 changes: 7 additions & 4 deletions src/app/modules/github-profile/state/github-profile.reducer.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import { GithubUserDto } from '../api/github-profile.api.typings'
import { GithubUserDto } from '../api/github-profile-api.typings'
import { RdxReducerBuilder } from '../../../shared/redux/redux-tools'
import { ghProfileActions } from './github-profile.actions'

export interface GhProfileState {
username: string
userDto: GithubUserDto | null
isLoading: boolean
error: unknown
}

export const ghProfileReducer = new RdxReducerBuilder<GhProfileState>()
.bindReducerWithAction(ghProfileActions.fetchRequested, () => {
.bindReducerWithAction(ghProfileActions.fetchRequested, payload => {
return {
username: payload.username,
isLoading: true,
}
})
.bindReducerWithAction(ghProfileActions.fetchComplete, (_, userDto) => {
.bindReducerWithAction(ghProfileActions.fetchComplete, userDto => {
return {
isLoading: false,
error: null,
userDto,
}
})
.bindReducerWithAction(ghProfileActions.fetchError, (_, error) => {
.bindReducerWithAction(ghProfileActions.fetchError, error => {
return {
isLoading: false,
error,
}
})
.build({
username: '',
userDto: null,
isLoading: false,
error: null,
Expand Down
6 changes: 3 additions & 3 deletions src/app/shared/redux/redux-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export const rdxActionCreator = <T extends string>(type: T) => {
}

export type RdxReducerFn<S, T extends string, P> = (
state: S,
payload: RdxAction<T, P>['payload']
payload: RdxAction<T, P>['payload'],
state: S
) => Partial<S>

export class RdxReducerBuilder<S> {
Expand All @@ -55,7 +55,7 @@ export class RdxReducerBuilder<S> {
if (reducerFn) {
return {
...state,
...reducerFn(state, action.payload),
...reducerFn(action.payload, state),
}
} else {
return state
Expand Down
12 changes: 12 additions & 0 deletions testcafe/specs/app-smoke.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Selector } from 'testcafe'
import { e2eConfig } from '../e2e-config'

fixture(`Getting Started`).page(`http://localhost:${e2eConfig.port}`)

test('WHEN open app THEN main header text visible [<App/> works]', async t => {
await t.expect(Selector('h1').innerText).eql('App component')
})

test('WHEN open app THEN rodmax-s github profile info visible [Redux works/github API works]', async t => {
await t.expect(Selector('pre').innerText).contains('"name": "Maxim Rodionov"')
})
12 changes: 0 additions & 12 deletions testcafe/specs/test.e2e.ts

This file was deleted.

0 comments on commit ec399d7

Please sign in to comment.