0
votes

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>
    )
  }
}
2

2 Answers

0
votes

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.

0
votes

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()),
  }
}