From 3754b1f51033a32b57d21ee37f3791d6c68593e8 Mon Sep 17 00:00:00 2001 From: Zach Posten Date: Wed, 28 Feb 2018 23:08:24 -0600 Subject: [PATCH] Convert the redux implementation to typescript Because I had to install a beta version of redux in order to get it to stop throwing dispatch type errors, based on advice from some issue, I had a subsequent problem with redux-thunk not yet supporting that new version. There was a pull request already though to fix this, so I pulled the changes from [that pr] into my redux-thunk npm package to get it to work. I have a comment on that PR as to exactly what I did. As of this moment though, that PR still isn't quite finished and thus this does not compile. [that pr]: https://github.com/gaearon/redux-thunk/pull/180 --- package.json | 21 ++++-- src/actions/actionTypes.js | 4 - src/actions/authorActions.js | 14 ---- src/actions/courseActions.js | 43 ----------- src/api/authorApi.js | 13 ---- src/api/authorApi.ts | 17 +++++ src/api/{baseUrl.js => baseUrl.ts} | 4 +- src/api/courseApi.js | 12 --- src/api/courseApi.ts | 15 ++++ src/api/{fetchHttp.js => fetchHttp.ts} | 10 +-- src/api/userApi.js | 9 --- src/components/Hello.tsx | 11 --- src/components/courses/CourseForm.js | 11 ++- src/components/courses/CoursesPage.js | 4 +- src/components/courses/ManageCoursePage.js | 2 +- src/index.js | 38 ---------- src/index.tsx | 43 +++++++++-- src/reducers/authorReducer.js | 12 --- src/reducers/index.js | 10 --- src/reducers/initialState.js | 9 --- src/redux/RootState.ts | 12 +++ src/redux/authors/authorActions.ts | 38 ++++++++++ src/redux/authors/authorReducer.ts | 14 ++++ src/redux/authors/index.ts | 3 + src/redux/authors/types.ts | 6 ++ src/redux/courses/courseActions.ts | 74 +++++++++++++++++++ .../courses/courseReducer.ts} | 11 ++- src/redux/courses/index.ts | 3 + src/redux/courses/types.ts | 17 +++++ src/redux/index.ts | 21 ++++++ src/redux/initialState.ts | 11 +++ src/redux/rootAction.ts | 7 ++ src/redux/rootReducer.ts | 10 +++ src/redux/store/configureStore.ts | 29 ++++++++ src/store/configureStore.js | 27 ------- 35 files changed, 351 insertions(+), 234 deletions(-) delete mode 100644 src/actions/actionTypes.js delete mode 100644 src/actions/authorActions.js delete mode 100644 src/actions/courseActions.js delete mode 100644 src/api/authorApi.js create mode 100644 src/api/authorApi.ts rename src/api/{baseUrl.js => baseUrl.ts} (80%) delete mode 100644 src/api/courseApi.js create mode 100644 src/api/courseApi.ts rename src/api/{fetchHttp.js => fetchHttp.ts} (87%) delete mode 100644 src/api/userApi.js delete mode 100644 src/components/Hello.tsx delete mode 100644 src/index.js delete mode 100644 src/reducers/authorReducer.js delete mode 100644 src/reducers/index.js delete mode 100644 src/reducers/initialState.js create mode 100644 src/redux/RootState.ts create mode 100644 src/redux/authors/authorActions.ts create mode 100644 src/redux/authors/authorReducer.ts create mode 100644 src/redux/authors/index.ts create mode 100644 src/redux/authors/types.ts create mode 100644 src/redux/courses/courseActions.ts rename src/{reducers/courseReducer.js => redux/courses/courseReducer.ts} (71%) create mode 100644 src/redux/courses/index.ts create mode 100644 src/redux/courses/types.ts create mode 100644 src/redux/index.ts create mode 100644 src/redux/initialState.ts create mode 100644 src/redux/rootAction.ts create mode 100644 src/redux/rootReducer.ts create mode 100644 src/redux/store/configureStore.ts delete mode 100644 src/store/configureStore.js diff --git a/package.json b/package.json index aace39c..17d2b99 100644 --- a/package.json +++ b/package.json @@ -24,19 +24,25 @@ "author": "Zach Posten", "license": "MIT", "dependencies": { - "@types/react": "^16.0.38", - "@types/react-dom": "^16.0.4", "history": "^4.7.2", "react": "^16.2.0", "react-dom": "^16.2.0", "react-redux": "^5.0.7", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", - "redux": "^3.7.2", - "redux-thunk": "^2.2.0", - "whatwg-fetch": "1.0.0" + "redux-thunk": "^2.2.0" }, "devDependencies": { + "@types/material-ui": "^0.21.1", + "@types/node": "^9.4.6", + "@types/prop-types": "^15.5.2", + "@types/react": "^16.0.38", + "@types/react-bootstrap": "^0.32.6", + "@types/react-dom": "^16.0.4", + "@types/react-hot-loader": "^3.0.6", + "@types/react-router-dom": "^4.2.4", + "@types/redux-immutable-state-invariant": "^2.0.4", + "@types/webpack-env": "^1.13.5", "autoprefixer": "^8.0.0", "awesome-typescript-loader": "^3.5.0", "babel-cli": "6.16.0", @@ -46,7 +52,7 @@ "babel-preset-react": "^6.24.1", "babel-register": "6.16.3", "chai": "3.5.0", - "chalk": "1.1.3", + "chalk": "^2.3.1", "cheerio": "0.22.0", "classnames": "^2.2.5", "compression": "1.6.2", @@ -73,12 +79,15 @@ "react-bootstrap": "^0.32.1", "react-hot-loader": "^3.1.3", "react-router-bootstrap": "^0.24.4", + "redux": "^4.0.0-beta.2", "redux-immutable-state-invariant": "^2.1.0", "rimraf": "2.5.4", "sass-loader": "^6.0.6", "source-map-loader": "^0.2.3", "style-loader": "0.13.1", + "typesafe-actions": "^1.1.2", "typescript": "^2.7.2", + "utility-types": "^1.0.0", "webpack": "^3.0.0", "webpack-dev-middleware": "1.11.0", "webpack-hot-middleware": "2.18.2", diff --git a/src/actions/actionTypes.js b/src/actions/actionTypes.js deleted file mode 100644 index fc93962..0000000 --- a/src/actions/actionTypes.js +++ /dev/null @@ -1,4 +0,0 @@ -export const LOAD_COURSES_SUCCESS = 'LOAD_COURSES_SUCCESS' -export const LOAD_AUTHORS_SUCCESS = 'LOAD_AUTHORS_SUCCESS' -export const CREATE_COURSE_SUCCESS = 'CREATE_COURSE_SUCCESS' -export const UPDATE_COURSE_SUCCESS = 'UPDATE_COURSE_SUCCESS' \ No newline at end of file diff --git a/src/actions/authorActions.js b/src/actions/authorActions.js deleted file mode 100644 index 904a03b..0000000 --- a/src/actions/authorActions.js +++ /dev/null @@ -1,14 +0,0 @@ -import authorApi from '../api/authorApi' -import * as types from './actionTypes' - -export function loadAuthorsSuccess(authors) { - return {type: types.LOAD_AUTHORS_SUCCESS, authors} -} - -export function loadAuthors() { - return dispatch => { - return authorApi.getAllAuthors().then(authors => { - dispatch(loadAuthorsSuccess(authors)) - }) - } -} \ No newline at end of file diff --git a/src/actions/courseActions.js b/src/actions/courseActions.js deleted file mode 100644 index 2cd1235..0000000 --- a/src/actions/courseActions.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as types from './actionTypes' -import courseApi from '../api/courseApi' - -/************************* - * ACTION CREATORS - *************************/ - -export function loadCoursesSuccess(courses) { - return { type: types.LOAD_COURSES_SUCCESS, courses } -} - -export function createCourseSuccess(course) { - return { type: types.CREATE_COURSE_SUCCESS, course } -} - -export function updateCourseSuccess(course) { - return { type: types.UPDATE_COURSE_SUCCESS, course } -} - -/************************* - * THUNKS - *************************/ - -export function loadCourses() { - return function(dispatch) { - let allCourses = courseApi.getAllCourses() - return allCourses.then(courses => { - dispatch(loadCoursesSuccess(courses)) - }) - } -} - -export function saveCourse(course) { - return function (dispatch, getState) { - return courseApi.saveCourse(course).then(savedCourse => { - if (course.id) { - dispatch(updateCourseSuccess(savedCourse)) - } else { - dispatch(createCourseSuccess(savedCourse)) - } - }) - } -} \ No newline at end of file diff --git a/src/api/authorApi.js b/src/api/authorApi.js deleted file mode 100644 index 397287d..0000000 --- a/src/api/authorApi.js +++ /dev/null @@ -1,13 +0,0 @@ -import { get } from './fetchHttp' -import courseApi from './courseApi' - -function getAllAuthors() { - return courseApi.getAllCourses().then(courses => { - let allAuthorEntries = courses.map(course => course.authorId) - - let uniqueAuthors = Array.from(new Set(allAuthorEntries)) - return uniqueAuthors.map((author) => ({id: author})) - }) -} - -export default { getAllAuthors } \ No newline at end of file diff --git a/src/api/authorApi.ts b/src/api/authorApi.ts new file mode 100644 index 0000000..5f75087 --- /dev/null +++ b/src/api/authorApi.ts @@ -0,0 +1,17 @@ +import { get } from './fetchHttp' +import { IAuthor, ICourse } from '../redux' +import courseApi from './courseApi' + + +class AuthorApi { + getAllAuthors() : Promise { + return courseApi.getAllCourses().then((courses: ICourse[]) => { + let allAuthorEntries = courses.map(course => course.authorId) + + let uniqueAuthors = Array.from(new Set(allAuthorEntries)) + return uniqueAuthors.map((author) => ({id: author})) + }) + } +} + +export default new AuthorApi() \ No newline at end of file diff --git a/src/api/baseUrl.js b/src/api/baseUrl.ts similarity index 80% rename from src/api/baseUrl.js rename to src/api/baseUrl.ts index 84155a8..2a68161 100644 --- a/src/api/baseUrl.js +++ b/src/api/baseUrl.ts @@ -1,4 +1,4 @@ -export default function getBaseUrl() { +export default function getBaseUrl(): string { return 'http://localhost:3001/' // return getQueryStringParameterByName('useMockApi') // ? 'http://localhost:3001/' @@ -6,7 +6,7 @@ export default function getBaseUrl() { } // This should really be done with a tested and maintained framework -// function getQueryStringParameterByName(name, url) { +// function getQueryStringParameterByName(name: string, url: string): string { // if (!url) url = window.location.href // name = name.replace(/[\[\]]/g, "\\$&") diff --git a/src/api/courseApi.js b/src/api/courseApi.js deleted file mode 100644 index 2fec521..0000000 --- a/src/api/courseApi.js +++ /dev/null @@ -1,12 +0,0 @@ -import { get, post } from './fetchHttp' - -export default { - getAllCourses: function() { - return get('courses') - }, - - saveCourse: function(course) { - console.log("Saved course: ", course) - return post('courses', course) - } -} \ No newline at end of file diff --git a/src/api/courseApi.ts b/src/api/courseApi.ts new file mode 100644 index 0000000..91652a3 --- /dev/null +++ b/src/api/courseApi.ts @@ -0,0 +1,15 @@ +import { get, post } from './fetchHttp' +import { ICourse } from '../redux' + +class CourseApi { + getAllCourses(): Promise { + return get('courses') + } + + saveCourse(course: ICourse): Promise { + console.log("Saved course: ", course) + return post('courses', course) + } +} + +export default new CourseApi() \ No newline at end of file diff --git a/src/api/fetchHttp.js b/src/api/fetchHttp.ts similarity index 87% rename from src/api/fetchHttp.js rename to src/api/fetchHttp.ts index b88c016..67fb870 100644 --- a/src/api/fetchHttp.js +++ b/src/api/fetchHttp.ts @@ -14,17 +14,17 @@ import getBaseUrl from './baseUrl' const baseUrl = getBaseUrl() -export function get(url) { +export function get(url: string) { return fetch(baseUrl + url).then(onSuccess, onError); } -export function del(url) { +export function del(url: string) { return fetch(baseUrl + url, { method: 'DELETE', }).then(onSuccess, onError) } -export function post(url, body) { +export function post(url: string, body: any) { // Default options are marked with * return fetch(baseUrl + url, { body: JSON.stringify(body), // must match 'Content-Type' header @@ -41,10 +41,10 @@ export function post(url, body) { .then(onSuccess, onError) } -function onSuccess(response) { +function onSuccess(response: Response) { return response.json(); } -function onError(error) { +function onError(error: any) { console.log(chalk.red(error), error); // eslint-disable-line no-console } \ No newline at end of file diff --git a/src/api/userApi.js b/src/api/userApi.js deleted file mode 100644 index 367b2b8..0000000 --- a/src/api/userApi.js +++ /dev/null @@ -1,9 +0,0 @@ -import { get, del } from './fetchHttp' - -export function getUsers() { - return get('users') -} - -export function deleteUser(id) { - return del('users/' + id); -} \ No newline at end of file diff --git a/src/components/Hello.tsx b/src/components/Hello.tsx deleted file mode 100644 index 84ce229..0000000 --- a/src/components/Hello.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from "react"; - -export interface HelloProps { compiler: string; framework: string; } - -// 'HelloProps' describes the shape of props. -// State is never set so we use the '{}' type. -export class Hello extends React.Component { - render() { - return (

Hello from {this.props.compiler} and {this.props.framework}!

); - } -} \ No newline at end of file diff --git a/src/components/courses/CourseForm.js b/src/components/courses/CourseForm.js index 1e31161..122edc4 100644 --- a/src/components/courses/CourseForm.js +++ b/src/components/courses/CourseForm.js @@ -4,7 +4,6 @@ import TextField from 'material-ui/TextField' import SelectField from 'material-ui/SelectField'; import MenuItem from 'material-ui/MenuItem'; import cs from 'classnames' -import chalk from 'chalk' import s from './CourseForm.scss' @@ -25,7 +24,7 @@ const CourseForm = ({ value={course.title} onChange={onChangeText} errorText={getErrorField(errors, 'title')} - fullWidth={true} + fullWidth /> - {allAuthors.map((author, index) => + {allAuthors.map(author => - ) } } @@ -52,6 +51,7 @@ class CoursesPage extends Component { CoursesPage.propTypes = { courses: PropTypes.array.isRequired, match: PropTypes.object.isRequired, // Supplied by react router + history: PropTypes.object.isRequired, // Supplied by react router } function mapStateToProps(state) { diff --git a/src/components/courses/ManageCoursePage.js b/src/components/courses/ManageCoursePage.js index 1e2a6b0..ab9e307 100644 --- a/src/components/courses/ManageCoursePage.js +++ b/src/components/courses/ManageCoursePage.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux' import { PropTypes } from 'prop-types' import { bindActionCreators } from 'redux' -import * as courseActions from '../../actions/courseActions' +import * as courseActions from '../../redux' import CourseForm from './CourseForm' class ManageCoursePage extends Component { diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 806e23a..0000000 --- a/src/index.js +++ /dev/null @@ -1,38 +0,0 @@ -/*eslint-disable import/default */ -import React from 'react' -import { render } from 'react-dom' -import { AppContainer } from 'react-hot-loader' -import configureStore from './store/configureStore' -import Root from './components/Root' -import { loadCourses } from './actions/courseActions' -import { loadAuthors } from './actions/authorActions' - -const store = configureStore() -store.dispatch(loadCourses()) -store.dispatch(loadAuthors()) - -const htmlRoot = document.getElementById('app') - -render( - // All children of will be hot reloaded when a change occurs - // When in production, AppContainer is automatically disabled, and simply - // returns its children. - - - , - htmlRoot -) - -// Hot Module Replacement API -// Must be placed directly below the call to react-dom#render -if (module.hot) { - module.hot.accept('./components/Root', () => { - const NewRoot = require('./components/Root').default; - render( - - - , - htmlRoot - ); - }); -} \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index b8aabd1..0ec2301 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,9 +1,40 @@ -import * as React from "react"; -import * as ReactDOM from "react-dom"; +/*eslint-disable import/default */ +import * as React from 'react' +import * as ReactDOM from "react-dom" +import { AppContainer } from 'react-hot-loader' +import { configureStore } from './redux' +import { loadCourses, loadAuthors } from './redux' +import { ThunkAction } from 'redux-thunk' -import { Hello } from "./components/Hello"; +//@ts-ignore +import { Root } from './components/Root' + +const store = configureStore() +store.dispatch(loadCourses()) +store.dispatch(loadAuthors()) + +const htmlRoot = document.getElementById('app') ReactDOM.render( - , - document.getElementById("app") -); \ No newline at end of file + // All children of will be hot reloaded when a change occurs + // When in production, AppContainer is automatically disabled, and simply + // returns its children. + + + , + htmlRoot +) + +// Hot Module Replacement API +// Must be placed directly below the call to react-dom#render +if (module.hot) { + module.hot.accept('./components/Root', () => { + const NewRoot = require('./components/Root').default; + ReactDOM.render( + + + , + htmlRoot + ); + }); +} \ No newline at end of file diff --git a/src/reducers/authorReducer.js b/src/reducers/authorReducer.js deleted file mode 100644 index 325aaa5..0000000 --- a/src/reducers/authorReducer.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as types from '../actions/actionTypes'; -import initialState from './initialState' - -export default function authorReducer(state = initialState.authors, action) { - switch (action.type) { - case types.LOAD_AUTHORS_SUCCESS: - return action.authors; - - default: - return state; - } -} diff --git a/src/reducers/index.js b/src/reducers/index.js deleted file mode 100644 index 00e4501..0000000 --- a/src/reducers/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import { combineReducers } from 'redux' -import courseReducer from './courseReducer' -import authorReducer from './authorReducer' - -const rootReducer = combineReducers({ - courses: courseReducer, - authors: authorReducer, -}) - -export default rootReducer \ No newline at end of file diff --git a/src/reducers/initialState.js b/src/reducers/initialState.js deleted file mode 100644 index 6c920ef..0000000 --- a/src/reducers/initialState.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * The object exported from this file depicts the overall structure - * of our redux store. Each reducer deals with a peice of the store, - * but this will give you an idea of what the whole thing looks like. - */ -export default { - authors: [], - courses: [], -} \ No newline at end of file diff --git a/src/redux/RootState.ts b/src/redux/RootState.ts new file mode 100644 index 0000000..ba0fa70 --- /dev/null +++ b/src/redux/RootState.ts @@ -0,0 +1,12 @@ +import { ICourse } from './courses' +import { IAuthor } from './authors' + +/* + * The interface exported from this file depicts the overall structure + * of our redux store. Each reducer deals with a peice of the store, + * but this will give you an idea of what the whole thing looks like. + */ +export type RootState = { + authors: IAuthor[], + courses: ICourse[], +} \ No newline at end of file diff --git a/src/redux/authors/authorActions.ts b/src/redux/authors/authorActions.ts new file mode 100644 index 0000000..d193842 --- /dev/null +++ b/src/redux/authors/authorActions.ts @@ -0,0 +1,38 @@ +import { ThunkResult } from '../' +import authorApi from '../../api/authorApi' +import { IAuthor, LOAD_AUTHORS_SUCCESS } from './types' +import { OtherAction } from '../' + +/************************* + * ACTION TYPES/CREATORS + *************************/ + +export type LoadAuthorsSuccessAction = { + type: LOAD_AUTHORS_SUCCESS, + authors: IAuthor[], +}; +export function loadAuthorsSuccess(authors: IAuthor[]): AuthorAction { + return {type: LOAD_AUTHORS_SUCCESS, authors} +} + + +/************************* + * ACTION TYPE UNION + *************************/ + +export type AuthorAction = + | LoadAuthorsSuccessAction + | OtherAction + ; + + +/************************* + * THUNKS + *************************/ + +export function loadAuthors() : ThunkResult { + return async (dispatch, getState) => { + const authors = await authorApi.getAllAuthors() + dispatch(loadAuthorsSuccess(authors)) + } +} \ No newline at end of file diff --git a/src/redux/authors/authorReducer.ts b/src/redux/authors/authorReducer.ts new file mode 100644 index 0000000..46ebbef --- /dev/null +++ b/src/redux/authors/authorReducer.ts @@ -0,0 +1,14 @@ +import * as types from './types' +import { RootState, AuthorAction } from '../' +import initialState from '../initialState' + + +export function authorReducer(state = initialState.authors, action: AuthorAction) { + switch (action.type) { + case types.LOAD_AUTHORS_SUCCESS: + return action.authors + + default: + return state + } +} \ No newline at end of file diff --git a/src/redux/authors/index.ts b/src/redux/authors/index.ts new file mode 100644 index 0000000..f5daa6f --- /dev/null +++ b/src/redux/authors/index.ts @@ -0,0 +1,3 @@ +export * from './authorActions' +export * from './types' +export * from './authorReducer' \ No newline at end of file diff --git a/src/redux/authors/types.ts b/src/redux/authors/types.ts new file mode 100644 index 0000000..ad5ea9d --- /dev/null +++ b/src/redux/authors/types.ts @@ -0,0 +1,6 @@ +export type LOAD_AUTHORS_SUCCESS = 'App/LOAD_AUTHORS_SUCCESS'; +export const LOAD_AUTHORS_SUCCESS : LOAD_AUTHORS_SUCCESS = 'App/LOAD_AUTHORS_SUCCESS'; + +export type IAuthor = { + id: string, +} \ No newline at end of file diff --git a/src/redux/courses/courseActions.ts b/src/redux/courses/courseActions.ts new file mode 100644 index 0000000..518d9a2 --- /dev/null +++ b/src/redux/courses/courseActions.ts @@ -0,0 +1,74 @@ +import * as types from './types' +import courseApi from '../../api/courseApi' + +import { + ICourse, + LOAD_COURSES_SUCCESS, + CREATE_COURSE_SUCCESS, + UPDATE_COURSE_SUCCESS, +} from './types' +import { OtherAction, ThunkResult } from '../' + +/************************* + * ACTION TYPES/CREATORS + *************************/ + +export type LoadCoursesSuccessAction = { + type: LOAD_COURSES_SUCCESS, + courses: ICourse[], +}; +export function loadCoursesSuccess(courses: ICourse[]): LoadCoursesSuccessAction { + return { type: types.LOAD_COURSES_SUCCESS, courses } +} + +export type CreateCourseSuccessAction = { + type: CREATE_COURSE_SUCCESS, + course: ICourse, +} +export function createCourseSuccess(course: ICourse): CreateCourseSuccessAction { + return { type: types.CREATE_COURSE_SUCCESS, course } +} + +export type UpdateCourseSuccessAction = { + type: UPDATE_COURSE_SUCCESS, + course: ICourse, +} +export function updateCourseSuccess(course: ICourse): UpdateCourseSuccessAction { + return { type: types.UPDATE_COURSE_SUCCESS, course } +} + + +/************************* + * ACTION TYPE UNION + *************************/ + +export type CourseAction = + | LoadCoursesSuccessAction + | CreateCourseSuccessAction + | UpdateCourseSuccessAction + | OtherAction + ; + +/************************* + * THUNKS + *************************/ + +export function loadCourses() : ThunkResult { + return async (dispatch, getState) => { + const allCourses = await courseApi.getAllCourses() + console.log('loaded courses', allCourses) + dispatch(loadCoursesSuccess(allCourses)) + return allCourses + } +} + +export function saveCourse(course: ICourse) : ThunkResult { + return async (dispatch, getState) => { + const savedCourse = await courseApi.saveCourse(course) + const action = course.id + ? updateCourseSuccess(savedCourse) + : createCourseSuccess(savedCourse) + + dispatch(action) + } +} \ No newline at end of file diff --git a/src/reducers/courseReducer.js b/src/redux/courses/courseReducer.ts similarity index 71% rename from src/reducers/courseReducer.js rename to src/redux/courses/courseReducer.ts index 93919d6..b9ff8a6 100644 --- a/src/reducers/courseReducer.js +++ b/src/redux/courses/courseReducer.ts @@ -1,5 +1,9 @@ -import * as types from '../actions/actionTypes' -import initialState from './initialState' +import * as types from './types' +import { ICourse, CourseAction } from '../' +import initialState from '../initialState' +import { LoadCoursesSuccessAction, + CreateCourseSuccessAction, + UpdateCourseSuccessAction } from './courseActions' /* * It's important to note that each reducer only handles @@ -9,8 +13,7 @@ import initialState from './initialState' * below is initialized to the 'courses' portion of the * initial state. */ - -export default function courseReducer(state=initialState.courses, action) { +export function courseReducer(state=initialState.courses, action: CourseAction) { switch(action.type) { case types.LOAD_COURSES_SUCCESS: return action.courses diff --git a/src/redux/courses/index.ts b/src/redux/courses/index.ts new file mode 100644 index 0000000..5f55c16 --- /dev/null +++ b/src/redux/courses/index.ts @@ -0,0 +1,3 @@ +export * from './courseActions' +export * from './types' +export * from './courseReducer' \ No newline at end of file diff --git a/src/redux/courses/types.ts b/src/redux/courses/types.ts new file mode 100644 index 0000000..2a07522 --- /dev/null +++ b/src/redux/courses/types.ts @@ -0,0 +1,17 @@ +export type ICourse = { + id: string, + title: string, + watchUrl: string, + authorId: string, + length: number, + category: string, +} + +export type LOAD_COURSES_SUCCESS = 'App/LOAD_COURSES_SUCCESS'; +export const LOAD_COURSES_SUCCESS : LOAD_COURSES_SUCCESS = 'App/LOAD_COURSES_SUCCESS'; + +export type CREATE_COURSE_SUCCESS = 'App/CREATE_COURSE_SUCCESS'; +export const CREATE_COURSE_SUCCESS : CREATE_COURSE_SUCCESS = 'App/CREATE_COURSE_SUCCESS'; + +export type UPDATE_COURSE_SUCCESS = 'App/UPDATE_COURSE_SUCCESS'; +export const UPDATE_COURSE_SUCCESS : UPDATE_COURSE_SUCCESS = 'App/UPDATE_COURSE_SUCCESS'; \ No newline at end of file diff --git a/src/redux/index.ts b/src/redux/index.ts new file mode 100644 index 0000000..137c934 --- /dev/null +++ b/src/redux/index.ts @@ -0,0 +1,21 @@ +export * from './authors' +export * from './courses' +export * from './store/configureStore' + +export * from './rootReducer' +export * from './rootState' +export * from './rootAction' + +export type OtherAction = { type: '' }; +export const OtherAction : OtherAction = { type: '' }; + +import { Action, ActionCreator } from 'redux' +import { ThunkAction } from 'redux-thunk' +import { RootState } from './rootState' +import { RootAction } from './rootAction' +/** + * A type for specifying the eventual return type of + * any Thunk. `T` is the eventual return type of + * the thunk. + */ +export type ThunkResult = ThunkAction, RootState, undefined, RootAction> \ No newline at end of file diff --git a/src/redux/initialState.ts b/src/redux/initialState.ts new file mode 100644 index 0000000..a7657f8 --- /dev/null +++ b/src/redux/initialState.ts @@ -0,0 +1,11 @@ +import { ICourse } from './courses' +import { IAuthor } from './authors' +import { RootState } from './RootState' + +// Note that this class is an instance of type RootState +let initialState : RootState = { + authors: [], + courses: [], +} + +export default initialState \ No newline at end of file diff --git a/src/redux/rootAction.ts b/src/redux/rootAction.ts new file mode 100644 index 0000000..747ec93 --- /dev/null +++ b/src/redux/rootAction.ts @@ -0,0 +1,7 @@ +import { AuthorAction } from './authors' +import { CourseAction } from './courses' + +export type RootAction = + | AuthorAction + | CourseAction + ; \ No newline at end of file diff --git a/src/redux/rootReducer.ts b/src/redux/rootReducer.ts new file mode 100644 index 0000000..c7b34ef --- /dev/null +++ b/src/redux/rootReducer.ts @@ -0,0 +1,10 @@ +import { combineReducers, Reducer, Dispatch, Store, Action } from 'redux' +import { RootState, courseReducer, authorReducer } from './' + + +const rootReducer: Reducer = combineReducers({ + courses: courseReducer, + authors: authorReducer, +}) + +export { rootReducer } \ No newline at end of file diff --git a/src/redux/store/configureStore.ts b/src/redux/store/configureStore.ts new file mode 100644 index 0000000..ab9d912 --- /dev/null +++ b/src/redux/store/configureStore.ts @@ -0,0 +1,29 @@ +import { createStore, applyMiddleware } from 'redux' +import reduxImmutableStateInvariant from 'redux-immutable-state-invariant' +import { RootState, rootReducer, RootAction } from '../' +import initialState from '../initialState' +import thunk, { ThunkAction, ThunkMiddleware } from 'redux-thunk' + +function configureStore(state: RootState = initialState) { + const middleware = applyMiddleware( + thunk as ThunkMiddleware, + reduxImmutableStateInvariant() + ) + + const store = createStore( + rootReducer, + middleware, + ) + + if (module.hot) { + // Enable Webpack hot module replacement for reducers + module.hot.accept('../rootReducer', () => { + const nextReducer = require('../rootReducer').default; // eslint-disable-line global-require + store.replaceReducer(nextReducer); + }) + } + + return store +} + +export { configureStore } \ No newline at end of file diff --git a/src/store/configureStore.js b/src/store/configureStore.js deleted file mode 100644 index c4bf083..0000000 --- a/src/store/configureStore.js +++ /dev/null @@ -1,27 +0,0 @@ -import { createStore, applyMiddleware } from 'redux' -import rootReducer from '../reducers' -import reduxImmutableStateInvariant from 'redux-immutable-state-invariant' -import thunk from 'redux-thunk' - -export default function configureStore(initialState) { - const middleware = applyMiddleware( - thunk, - reduxImmutableStateInvariant() - ) - - const store = createStore( - rootReducer, - initialState, - middleware - ) - - if (module.hot) { - // Enable Webpack hot module replacement for reducers - module.hot.accept('../reducers', () => { - const nextReducer = require('../reducers').default; // eslint-disable-line global-require - store.replaceReducer(nextReducer); - }) - } - - return store -} \ No newline at end of file