Search code examples
typescriptreact-reduxredux-thunkreact-tsx

React Redux Thunk with Typescript making api request


I have been trying to make a simple URL request to a third party API. Here is the project I am working on (https://github.com/JMStudiosJoe/ReactPractice/tree/JMStudiosReact). I have been watching Dan Abramov videos, followed this example from redux docs and this example and seem to be missing something small since the logs show that my data from the API was recieved and dispatched to the reducer but the state for some reason does not get sent back to the component. Below is a summary of code:

//app.tsx: get the store and use Provider from react-redux

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import store from './redux/store/store'
import { Provider } from 'react-redux'
import { getAddressData } from './redux/actions/voteSmartActions'
import {UIRouter, UIView, UISref, UISrefActive, pushStateLocationPlugin} from 'ui-router-react';
import NavigationBarComponent from './components/navigationBarComponent'

store.dispatch(getAddressData(''))

ReactDOM.render(
    <Provider store={store}>
        <NavigationBarComponent userType='buyer' />
    </Provider>,
    document.getElementById("root")
);

The component in question gets loaded from the projectsComponent.tsx file

if(projectName == 'Vote Smart Locally' ) {
        return (
            <VoteSmartLocallyComponent />
        )
}

//voteSmartLocallyComponent.tsx

import * as React from 'react'
import { connect } from 'react-redux'
import { getAddressData } from '../../redux/actions/voteSmartActions'
import store from "../../redux/store/store"
interface VoteSmartState {
    address: string
    userAddressData?: any
}
interface VoteSmartProps {
    fetchAddressData: any 
}
const API_KEY = 'AIzaSyCWhwRupMs7IeE4IrGEgHtT0Nt-IGZnP9E'
const endURL = '&key='+ API_KEY
const baseRepURL = 'https://www.googleapis.com/civicinfo/v2/representatives?address='
const baseElectionsURL = 'https://www.googleapis.com/civicinfo/v2/elections?alt=json&prettyPrint=true'
class VoteSmartLocallyComponent extends React.Component<VoteSmartProps, VoteSmartState> {
    constructor(props) {
        super(props)
        console.log(props)
        this.state = {
            address: '',
            userAddressData: {}
        }
    }
    removeSpacesAddPluses() {
        return this.state.address.split(' ').join('+')      
    }
    lookupAddress(event: React.MouseEvent<HTMLButtonElement>) {
        event.preventDefault()
        const address = this.removeSpacesAddPluses()
        const fullRepURL = baseRepURL + address + endURL
        const fullElectionsURL = baseElectionsURL + address + endURL
        this.props.fetchAddressData(fullRepURL)
            /*
        store.subscribe(this.render)
        store.dispatch({
            type: 'LOOKUP_ADDRESS',
            payload: address
        })
             */
    }

    handleAddress(event: React.ChangeEvent<HTMLInputElement>) {
        event.preventDefault()
        const address = event.target.value
        this.setState({
            address: address 
        })
    }

    render() {
        return (
        <div>
            {console.log('log in the render method')}
            {console.log(this.state)}
            vote smart kids
            need to connect the redux and suff to make request
            <input 
                type='text' 
                placeholder='Address'
                onChange={ e => this.handleAddress(e) }
            />
            <button
                onClick={ e => this.lookupAddress(e) }
            >
            Submit for info
            </button>
        </div>
        )
    }

}

const mapStateToProps = (state) => {
    return {
        address: '',
        userAddressData: {}
    }
}
const mapDispatchToProps = (dispatch) => {
    return {
        fetchAddressData: (url) => dispatch(getAddressData(url))
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(VoteSmartLocallyComponent)

I am able to call this.props.fetchAddressData(fullRepURL) which calls my action

//voteSmartActions.tsx

import {Action} from 'redux'
import store from '../store/store'
import axios from 'axios'

interface VoteSmartAction<Action> {
    type: string
    payload?: any
}
const getAddressData = (fullURL: string) => {
    return function(dispatch, getState) {
        if (fullURL !== '') { 
            return axios.get(fullURL).then(function (response) {
                dispatch(addressDataSuccess(response))
            
            }).catch(function (error) {
                  console.log(error)
            })
        }
    }
}
const addressDataSuccess = (addressData: any) => {
    return {
        type: 'HANDLE_RETURN_DATA',
        payload: addressData,
        addressData
    }
}
export {
    getAddressData, VoteSmartAction
}

And from there goes to my reducer

//voteSmartReducer.tsx

import {Action} from 'redux'
import {VoteSmartAction, getAddressData} from '../actions/voteSmartActions'
import axios from 'axios'
const INITIAL_STATE: any = {
    address: '',
    userAddressData: {}
}
const VoteSmartReducer = (state: any = INITIAL_STATE, action: VoteSmartAction<Action>): any => {
    switch(action.type) {

        case "HANDLE_RETURN_DATA":
            console.log("in reducer handling return payload is")
            const returnData = {
                ...state,
                userAddressData: action.payload 
            }
            console.log(returnData)
            
            return returnData

        default:
            return state
    }
}
export default VoteSmartReducer

From there the state I made in the reducer should be returned back to the component with the data I fetched but it isn't. I would appreciate any advice or help, thanks.


Solution

  • Currently you're passing in an empty object for userAddressData and empty string for address in mapStateToProps. So your component will always have these values.

    You need specify within mapStateToProps, where the data lies within the state tree.

    Look at the shape of your reducer, and see where where the data sits within the state tree, and then map it in the following way within mapStateToProps.

    Example

    index.js

    import { combineReducers } from 'redux'
    import voteSmartReducer from './VoteSmartReducer' // assuming this is in same directory
    import someOtherReducer from './someOtherReducer' // example purposes
    
    export default combineReducers({
        userAddressData: voteSmartReducer,
        otherExample: someOtherReducer
    })
    

    As you can see, the data returned by the voteSmartReducer is mapped to the key userAddressData in combineReducers. So state.userAddressData points to this. This is how you would map the state to props.

    const mapStateToProps = (state) => {
        return {
            userAddressData: state.userAddressData,
            otherExample: state.otherExample
        }
    }
    

    When you create the store, you import the reducer from index.js and pass it in as the first argument to createStore.

    Example.

    import { createStore } from 'redux'
    import reducers from './index.js'
    
    const store = createStore(
            reducers
    )