Reputation: 21
I am using React, Redux, Redux-Thunk, and Typescript(noobie). When I call an action with a thunk, I can see that the action is called (using a console.log) but the dispatch is not. I have connected this action using mapToDispatch and call it from the Component using this.props.(action) . I can't figure out why the dispatch is not being called. Here is my code:
store.ts
import { applyMiddleware, combineReducers, createStore, Store, compose } from 'redux'
import { ReducerMap, StoreShape, SYSTEM_STATE_NAMESPACE_KEY } from './types'
import thunkMiddleware from 'redux-thunk'
import systemReducer from './globalData/reducer'
import { Environment, getCurrentEnvironment } from '../services/api.service'
let reducerMap: ReducerMap = { [SYSTEM_STATE_NAMESPACE_KEY]: systemReducer }
const isDevelopmentEnvironment = [Environment.LOCALHOST, Environment.STAGING].includes(getCurrentEnvironment())
export default function configureStore() {
const middleware = [thunkMiddleware]
const middlewareEnhancer = applyMiddleware(...middleware)
let composedEnhancers = isDevelopmentEnvironment
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
: compose
const preloadedState = (window as any).__PRELOADED_STATE__;
delete (window as any).__PRELOADED_STATE__;
return createStore(combineReducers(reducerMap), preloadedState, composedEnhancers(middlewareEnhancer))
}
export const store = configureStore()
export function getStore() {
return store
}
actions.js
import { SearchActionTypes, SearchNamespaceShape } from './types'
import axios from '../../../services/axiosInstance'
import { Action } from 'redux'
import { StoreShape } from '../../../store/types'
import { getAPIDomain } from '../../../services/api.service'
export function getSearchResults (): ThunkAction<void, StoreShape, void, Action> {
console.log('inside function')
return (dispatch) => {
console.log('inside dispatch')
const body: object = {
"query": {
"bool": {
"must": [
{
"match": {
"title": "CEO"
}
}
]
}
}
}
axios.post(
'https://' + getAPIDomain() + '/proxy-service/ROOT/search/_search',
body
)
.then((response: object):any => console.log(response))
.catch((response: object):any => console.log(response))
}
}
container
import { connect, Provider } from 'react-redux'
import * as React from 'react'
import { getStoreForSearch } from './data/store'
import { getGlobalData } from '../../store/globalData/selectors'
import UnconnectedSearchPage, { StateProps, DispatchProps, OwnProps } from './search'
import { StoreShape } from '../../store/types'
import { getSearchResults } from './data/actions'
const SearchContainer: React.FC = () => {
return (
<Provider store={getStoreForSearch({})} >
<ConnectedSearchPage textToDisplay='Hello World'/>
</Provider>)
}
function mapStateToProps (state: StoreShape, ownProps: OwnProps): StateProps {
return(
{
system: getGlobalData(state)
}
)
}
const mapDispatchToProps = (dispatch: any, ownProps: OwnProps): DispatchProps => ({
getSearchResults: () => dispatch(getSearchResults)
})
const ConnectedSearchPage = connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
UnconnectedSearchPage
)
export default SearchContainer
component
import React from 'react'
import { ThunkAction } from 'redux-thunk'
import { Action } from 'redux'
import { GlobalDataNamespaceShape } from '../../store/globalData/types'
import { FullStoreShape } from '../../store/types'
export interface OwnProps {
textToDisplay: string
labelText?: string
}
export interface StateProps {
system: GlobalDataNamespaceShape
}
export interface DispatchProps {
getSearchResults: () => ThunkAction<void, Partial<FullStoreShape>, undefined, Action<object>>
}
export type SearchComponentProps = StateProps & DispatchProps & OwnProps
interface SearchState {
greeting: string
}
export default class UnconnectedSearchPage extends React.Component<SearchComponentProps, SearchState> {
constructor(props: SearchComponentProps) {
super(props)
this.state = { greeting: props.textToDisplay }
}
setGreeting( greeting: string): void {
this.setState({ greeting })
}
render () {
console.log(this.props)
return (
<div>
<h2>Search Page</h2>
<div>{`Greeting: ${this.state.greeting}`}</div>
<label>{this.props.labelText}</label>
<input
type='text'
value={this.state.greeting}
onChange={event => this.setGreeting(event.target.value)}
/>
<button onClick={() => {
this.props.getSearchResults()
}}>Get Search Results</button>
</div>
)
}
}
Upvotes: 1
Views: 1480
Reputation: 21
I found that mapping my dispatch like this fixed my problem - but I am not 100% sure why ... :
const mapDispatchToProps = (dispatch: any, ownProps: OwnProps): DispatchProps => {
return {
getSearchResults: () => dispatch(getSearchResults()),
}
}
Upvotes: 0
Reputation: 726
It seems like you're dispatching your thunk action okay, but you aren't then actually dispatching an action within the thunk action.
When you dispatch getSearchResults
, a thunk action, you're being returned a function that gets the dispatch
method as a parameter, which you've noted correctly with return (dispatch) => {
. You can use that dispatch method to dispatch further actions.
You have to call that dispatch method for it to have effect. Probably where you'll want to do this is inside the promise callback to do something with the data returned from the API call and then store it in redux. Something like...
export function getSearchResults (): ThunkAction<void, StoreShape, void, Action> {
return (dispatch) => {
const body: object = {
...
}
axios.post(
'https://' + getAPIDomain() + '/proxy-service/ROOT/search/_search',
body
)
.then((response: object): void => {
const data = extractData(response)
dispatch({
type: UPDATE_SEARCH_RESULTS,
payload: data,
})
})
.catch((response: object):any => console.log(response))
}
}
You should then receive that UPDATE_SEARCH_RESULTS
action in a reducer.
If you're going to be using redux actions for your API calls a lot, I strongly recommend using a middleware like this https://www.npmjs.com/package/redux-api-middleware to keep it clean for you.
Upvotes: 1