converted redux store to ts

This commit is contained in:
andres alcocer
2022-11-26 10:52:48 -05:00
parent f12c466c6c
commit a023650751
21 changed files with 355 additions and 134 deletions

68
package-lock.json generated
View File

@@ -9,10 +9,12 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@reduxjs/toolkit": "^1.9.0",
"@types/jest": "^29.2.3",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@types/react-redux": "^7.1.24",
"axios": "^1.2.0",
"dotenv": "^16.0.3",
"firebase": "^9.14.0",
@@ -3929,6 +3931,29 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"node_modules/@reduxjs/toolkit": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.0.tgz",
"integrity": "sha512-ak11IrjYcUXRqlhNPwnz6AcvA2ynJTu8PzDbbqQw4a3xR4KZtgiqbNblQD+10CRbfK4+5C79SOyxnT9dhBqFnA==",
"dependencies": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@remix-run/router": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.3.tgz",
@@ -4944,6 +4969,17 @@
"@types/react": "*"
}
},
"node_modules/@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -21112,6 +21148,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
},
"node_modules/resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
@@ -27617,6 +27658,17 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"@reduxjs/toolkit": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.0.tgz",
"integrity": "sha512-ak11IrjYcUXRqlhNPwnz6AcvA2ynJTu8PzDbbqQw4a3xR4KZtgiqbNblQD+10CRbfK4+5C79SOyxnT9dhBqFnA==",
"requires": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
}
},
"@remix-run/router": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.3.tgz",
@@ -28410,6 +28462,17 @@
"@types/react": "*"
}
},
"@types/react-redux": {
"version": "7.1.24",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -40182,6 +40245,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
},
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",

View File

@@ -13,10 +13,12 @@
"author": "Andres Alcocer",
"license": "ISC",
"dependencies": {
"@reduxjs/toolkit": "^1.9.0",
"@types/jest": "^29.2.3",
"@types/node": "^18.11.9",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@types/react-redux": "^7.1.24",
"axios": "^1.2.0",
"dotenv": "^16.0.3",
"firebase": "^9.14.0",

View File

@@ -1,40 +1,40 @@
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import * as movieActions from '../store/actions'
import * as actionMovies from '../store/slices/actionMovieSlice'
import { useAppSelector, useAppDispatch } from '../store'
import Header from './Header'
import DisplayMovieRow from './DisplayMovieRow'
const MainContent = ({ selectMovieHandler }) => {
const { movieDetails } = useSelector((state) => state.movieDetails)
const netflixOriginals = useSelector((state) => state.netflixOriginals)
const trending = useSelector((state) => state.trending)
const topRated = useSelector((state) => state.topRated)
const actionMovies = useSelector((state) => state.action)
const comedyMovies = useSelector((state) => state.comedy)
const horrorMovies = useSelector((state) => state.horror)
const romanceMovies = useSelector((state) => state.romance)
const documentaries = useSelector((state) => state.documentary)
// const { movieDetails } = useSelector((state) => state.movieDetails)
// const netflixOriginals = useSelector((state) => state.netflixOriginals)
// const trending = useSelector((state) => state.trending)
// const topRated = useSelector((state) => state.topRated)
const actionMoviesState = useAppSelector((state) => state.action)
// const comedyMovies = useSelector((state) => state.comedy)
// const horrorMovies = useSelector((state) => state.horror)
// const romanceMovies = useSelector((state) => state.romance)
// const documentaries = useSelector((state) => state.documentary)
const dispatch = useDispatch()
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(movieActions.fetchMovieDetails('tv', '63351'))
dispatch(movieActions.fetchNetflixOriginals())
dispatch(movieActions.fetchTrending())
dispatch(movieActions.fetchTopRated())
dispatch(movieActions.fetchActionMovies())
dispatch(movieActions.fetchComedyMovies())
dispatch(movieActions.fetchHorrorMovies())
dispatch(movieActions.fetchRomanceMovies())
dispatch(movieActions.fetchDocumentaries())
// dispatch(movieActions.fetchMovieDetails('tv', '63351'))
// dispatch(movieActions.fetchNetflixOriginals())
// dispatch(movieActions.fetchTrending())
// dispatch(movieActions.fetchTopRated())
dispatch(actionMovies.getActionMoviesAsync())
// dispatch(movieActions.fetchComedyMovies())
// dispatch(movieActions.fetchHorrorMovies())
// dispatch(movieActions.fetchRomanceMovies())
// dispatch(movieActions.fetchDocumentaries())
}, [dispatch])
return (
<div className='container'>
<Header movie={movieDetails} />
{/* <Header movie={movieDetails} /> */}
<div className='movieShowcase'>
<DisplayMovieRow
{/* <DisplayMovieRow
isNetflixMovies={true}
title='Netflix Originals'
selectMovieHandler={selectMovieHandler}
@@ -49,13 +49,13 @@ const MainContent = ({ selectMovieHandler }) => {
title='Top Rated'
selectMovieHandler={selectMovieHandler}
movies={topRated.data}
/>
/> */}
<DisplayMovieRow
title='Action Movies'
selectMovieHandler={selectMovieHandler}
movies={actionMovies.data}
movies={actionMoviesState.data}
/>
<DisplayMovieRow
{/* <DisplayMovieRow
title='Comedy'
selectMovieHandler={selectMovieHandler}
movies={comedyMovies.data}
@@ -74,7 +74,7 @@ const MainContent = ({ selectMovieHandler }) => {
title='Documentaries'
selectMovieHandler={selectMovieHandler}
movies={documentaries.data}
/>
/> */}
</div>
</div>
)

View File

@@ -1,11 +1,9 @@
import React from 'react'
import * as ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import ReduxThunk from 'redux-thunk'
import '@babel/polyfill'
import reducers from './store/reducers'
import { store } from './store'
import AppRouter from './AppRouter'
// Import Swiper styles
@@ -15,8 +13,6 @@ import 'swiper/css/pagination'
// Import main sass file to apply global styles
import './static/sass/style.scss'
const store = createStore(reducers, applyMiddleware(ReduxThunk))
const app = (
<Provider store={store}>
<AppRouter />

View File

@@ -1,11 +1,18 @@
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useLocation } from 'react-router-dom'
import { useAppSelector, useAppDispatch } from '../store'
import ModalMovieDetails from '../components/ModalMovieDetails'
import Modal from '../components/UI/Modal'
import { useDebounce } from '../hooks/useDebounce'
import * as movieActions from '../store/actions'
interface IMovie {
id: string
media_type?: string
backdrop_path?: string
}
// A custom hook that builds on useLocation to parse
// the query string for you.
const useQuery = () => {
@@ -16,9 +23,11 @@ const Search = () => {
let query = useQuery()
const debouncedSearchTerm = useDebounce(query.get('q'), 500)
const [isToggleModal, setIsToggleModal] = useState(false)
const { searchResults, isLoading } = useSelector((state) => state.searchMovie)
const { movieDetails } = useSelector((state) => state.movieDetails)
const dispatch = useDispatch()
const { searchResults, isLoading } = useAppSelector(
(state) => state.searchMovie
)
const { movieDetails } = useAppSelector((state) => state.movieDetails)
const dispatch = useAppDispatch()
useEffect(() => {
if (debouncedSearchTerm) {
@@ -30,7 +39,7 @@ const Search = () => {
setIsToggleModal(false)
}
const onSelectMovieHandler = (movie) => {
const onSelectMovieHandler = (movie: IMovie) => {
dispatch(movieActions.fetchMovieDetails(movie.media_type, movie.id))
setIsToggleModal(true)
}
@@ -39,7 +48,7 @@ const Search = () => {
return searchResults.length > 0 ? (
<>
<div className='search-container'>
{searchResults.map((movie) => {
{searchResults.map((movie: IMovie) => {
if (movie.backdrop_path !== null && movie.media_type !== 'person') {
const movieImageUrl =
'https://image.tmdb.org/t/p/w500' + movie.backdrop_path

View File

@@ -31,7 +31,6 @@ export const fetchMovieDetails = (mediaType: string, mediaId: string) => {
urlPath = `/movie/${mediaId}?api_key=${process.env.API_KEY}`
if (mediaType === media_type.tv)
urlPath = `/tv/${mediaId}?api_key=${process.env.API_KEY}`
console.log('media type', mediaType, urlPath)
const request = await axios.get(urlPath)
dispatch({ type: FETCH_MOVIE_DETAILS_SUCCESS, payload: request })
} catch (error) {

33
src/store/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import { configureStore } from '@reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import TrendingReducer from './slices/reducerTrending'
import NetflixOriginalsReducer from './slices/reducerNetflixOriginals'
import TopRatedReducer from './slices/reducerTopRated'
import ActionMoviesReducer from './slices/actionMovieSlice'
import ComedyMoviesReducer from './slices/reducerComedyMovies'
import HorrorMoviesReducer from './slices/reducerHorrorMovies'
import RomanceMoviesReducer from './slices/reducerRomanceMovies'
import DocumentaryReducer from './slices/reducerDocumentary'
import SearchMovieReducer from './slices/searchSlice'
import MovieDetailsReducer from './slices/movieDetailsSlice'
export const store = configureStore({
reducer: {
// trending: TrendingReducer,
// netflixOriginals: NetflixOriginalsReducer,
// topRated: TopRatedReducer,
action: ActionMoviesReducer,
// comedy: ComedyMoviesReducer,
// horror: HorrorMoviesReducer,
// romance: RomanceMoviesReducer,
searchMovie: SearchMovieReducer,
// documentary: DocumentaryReducer,
movieDetails: MovieDetailsReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

View File

@@ -1,26 +0,0 @@
import { combineReducers } from 'redux'
import TrendingReducer from './reducerTrending'
import NetflixOriginalsReducer from './reducerNetflixOriginals'
import TopRatedReducer from './reducerTopRated'
import ActionMoviesReducer from './reducerActionMovies'
import ComedyMoviesReducer from './reducerComedyMovies'
import HorrorMoviesReducer from './reducerHorrorMovies'
import RomanceMoviesReducer from './reducerRomanceMovies'
import DocumentaryReducer from './reducerDocumentary'
import SearchMovieReducer from './reducerSearchMovie'
import MovieDetailsReducer from './reducerMovieDetails'
const rootReducer = combineReducers({
trending: TrendingReducer,
netflixOriginals: NetflixOriginalsReducer,
topRated: TopRatedReducer,
action: ActionMoviesReducer,
comedy: ComedyMoviesReducer,
horror: HorrorMoviesReducer,
romance: RomanceMoviesReducer,
documentary: DocumentaryReducer,
searchMovie: SearchMovieReducer,
movieDetails: MovieDetailsReducer,
})
export default rootReducer

View File

@@ -1,11 +0,0 @@
import { FETCH_ACTION_MOVIES } from '../actions/index'
export default function (state = {}, action: any) {
switch (action.type) {
case FETCH_ACTION_MOVIES:
const data = action.payload.data.results
return { ...state, data }
default:
return state
}
}

View File

@@ -1,29 +0,0 @@
import {
FETCH_MOVIE_DETAILS,
FETCH_MOVIE_DETAILS_SUCCESS,
FETCH_MOVIE_DETAILS_FAIL,
} from '../actions/index'
interface IInitialState {
isLoading: boolean
movieDetails: []
}
const initialState: IInitialState = {
isLoading: false,
movieDetails: [],
}
export default function (state = initialState, action: any) {
switch (action.type) {
case FETCH_MOVIE_DETAILS:
return { ...state, isLoading: true }
case FETCH_MOVIE_DETAILS_FAIL:
return { ...state, isLoading: false }
case FETCH_MOVIE_DETAILS_SUCCESS:
const movieDetails = action.payload.data
return { ...state, movieDetails, isLoading: false }
default:
return state
}
}

View File

@@ -1,29 +0,0 @@
import {
FETCH_SEARCH_MOVIE,
FETCH_SEARCH_MOVIE_FAIL,
FETCH_SEARCH_MOVIE_SUCCESS,
} from '../actions/index'
interface IInitialState {
isLoading: boolean
searchResults: []
}
const initialState: IInitialState = {
isLoading: false,
searchResults: [],
}
export default function (state = initialState, action: any) {
switch (action.type) {
case FETCH_SEARCH_MOVIE:
return { ...state, isLoading: true }
case FETCH_SEARCH_MOVIE_FAIL:
return { ...state, isLoading: false }
case FETCH_SEARCH_MOVIE_SUCCESS:
const searchResults = action.payload.data.results
return { ...state, searchResults, isLoading: false }
default:
return state
}
}

View File

@@ -0,0 +1,43 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from '../../axios-movies'
// import { FETCH_ACTION_MOVIES } from '../actions/index'
import { RootState } from '../index'
// export default function (state = {}, action: any) {
// switch (action.type) {
// case FETCH_ACTION_MOVIES:
// const data = action.payload.data.results
// return { ...state, data }
// default:
// return state
// }
// }
const initialState = {
data: [{}],
}
export const getActionMoviesAsync = createAsyncThunk<
any,
void,
{ state: RootState }
>('action/getActionMovies', async () => {
const response = await axios.get(
`/discover/movie?api_key=${process.env.API_KEY}&with_genres=28`
)
return response.data.results
})
const actionMovieSlice = createSlice({
name: 'actionMovie',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getActionMoviesAsync.fulfilled, (state, { payload }) => {
state.data = payload
})
},
})
export default actionMovieSlice.reducer

View File

@@ -0,0 +1,100 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from '../../axios-movies'
import { RootState } from '../index'
import {
FETCH_MOVIE_DETAILS,
FETCH_MOVIE_DETAILS_SUCCESS,
FETCH_MOVIE_DETAILS_FAIL,
} from '../actions/index'
import { string } from 'prop-types'
interface IMovieDetails {
backdrop_path?: string
poster_path?: string
title: any
name: any
vote_average: any
release_date: any
first_air_date: any
runtime: any
episode_run_time: any
number_of_episodes: any
number_of_seasons: any
overview: any
}
interface IInitialState {
isLoading: boolean
movieDetails: IMovieDetails
}
const media_type = {
tv: 'tv',
movie: 'movie',
}
// const initialState: IInitialState = {
// isLoading: false,
// movieDetails: [],
// }
// export default function (state = initialState, action: any) {
// switch (action.type) {
// case FETCH_MOVIE_DETAILS:
// return { ...state, isLoading: true }
// case FETCH_MOVIE_DETAILS_FAIL:
// return { ...state, isLoading: false }
// case FETCH_MOVIE_DETAILS_SUCCESS:
// const movieDetails = action.payload.data
// return { ...state, movieDetails, isLoading: false }
// default:
// return state
// }
// }
const initialState: IInitialState = {
isLoading: false,
movieDetails: {
backdrop_path: '',
poster_path: '',
title: '',
name: '',
vote_average: '',
release_date: '',
first_air_date: '',
runtime: '',
episode_run_time: '',
number_of_episodes: '',
number_of_seasons: '',
overview: '',
},
}
export const getMovieDetailsAsync = createAsyncThunk<
any,
string,
{ state: RootState }
>('movieDetails/getMovieDetailsAsync', async (mediaType, mediaId) => {
let urlPath
if (mediaType === media_type.movie)
urlPath = `/movie/${mediaId}?api_key=${process.env.API_KEY}`
if (mediaType === media_type.tv)
urlPath = `/tv/${mediaId}?api_key=${process.env.API_KEY}`
const response = await axios.get(urlPath)
return response.data.results
})
const movieDetailsSlice = createSlice({
name: 'movieDetails',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getMovieDetailsAsync.fulfilled, (state, { payload }) => {
state.isLoading = false
state.movieDetails = payload
})
},
})
export default movieDetailsSlice.reducer

View File

@@ -0,0 +1,66 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from '../../axios-movies'
import { RootState } from '../index'
import {
FETCH_SEARCH_MOVIE,
FETCH_SEARCH_MOVIE_FAIL,
FETCH_SEARCH_MOVIE_SUCCESS,
} from '../actions/index'
// interface IInitialState {
// isLoading: boolean
// searchResults: []
// }
// const initialState: IInitialState = {
// isLoading: false,
// searchResults: [],
// }
// export default function (state = initialState, action: any) {
// switch (action.type) {
// case FETCH_SEARCH_MOVIE:
// return { ...state, isLoading: true }
// case FETCH_SEARCH_MOVIE_FAIL:
// return { ...state, isLoading: false }
// case FETCH_SEARCH_MOVIE_SUCCESS:
// const searchResults = action.payload.data.results
// return { ...state, searchResults, isLoading: false }
// default:
// return state
// }
// }
const initialState = {
searchResults: [{}],
isLoading: true,
}
export const searchItemsAsync = createAsyncThunk<
any,
void,
{ state: RootState }
>('search/getSearchItems', async (searchTerm) => {
const response = await axios.get(
`/search/multi?api_key=${process.env.API_KEY}&language=en-US&include_adult=false&query=${searchTerm}`
)
return response.data.results
})
const searchSlice = createSlice({
name: 'search',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(searchItemsAsync.fulfilled, (state, { payload }) => {
state.isLoading = false
state.searchResults = payload
})
builder.addCase(searchItemsAsync.pending, (state) => {
state.isLoading = true
})
},
})
export default searchSlice.reducer