Search code examples
reactjsreduxredux-thunk

Redux-React: value returned by a dispatch function is not being passed to child component


I am new to react-redux actually so I just need some help trying to understand any syntactical or logical mistake I am making here...

Basically, I want to display the reports fetched from another server. Function 'fetchNames' handles that and function 'fetchdownloadable' creates a returns a link that allows to pass through authentication and download the report in pdf onClick.

So I debugged on console, and I believe that there is something wrong with the order of execution of the code... when i debug.. i noticed that : fetchnames is executed first to fetch all report data in json array... then fetchDownloadable is called for each object in the reports array and that returns 'undefined' value on SampleChild... then SampleChild is executed with undefined value... and then we come back to execute the fetchDownloadable function that prints link value on the console and dispatches action on the reducer that returns the 'state' instead of 'action.reports_uri' in the reducer...

Thanks in advance!

SampleParent.js

import React, {Component} from 'react'
import { connect } from 'react-redux'
import { fetchNames, fetchDownloadable } from '../../actions/actions'
import SampleChild from '../ui/SampleChild'
class SampleParent extends Component {
constructor(props) {
    super(props);
    this.fetchDownloadLink = this.fetchDownloadLink.bind(this)
}
componentDidMount() {
    const { dispatch } = this.props
    dispatch(fetchNames())
}
fetchDownloadLink(uri){
    this.props.dispatch(fetchDownloadable(uri))
}
render() {
    return (<div><ul id="myUL">
                {this.props.reports.map((report) => (
                    <li><SampleChild
                            key={report.id}
                            label={report.label}
                            uri={this.fetchDownloadLink("http://localhost:8080/sample"+report.uri+".pdf")}
                        /></li>))}
            </ul></div>
    )}
}
function mapStateToProps(state) {
const { reports } = state
return {
    reports
}}
export default connect(mapStateToProps)(SampleParent)

SampleChild.js

import React, { Component } from 'react'
export default class SampleChild extends Component {
render() {
    const { key, label, uri } = this.props
    return (
    <div className="inside_SampleChild" id={label}>
        {label}
        <a href={uri}><img src="../images/pdf-file_128.png" height="25px" width="25px"></img></a>
    </div>
    )}}

Action.js

import C from '../constants'
import fetch from 'isomorphic-fetch'  

export const fetchNames = value => dispatch => {
var obj = { method: 'GET', headers: { 'Authorization': 'Basic ***', 'Accept': 'application/json' },
    'credentials': 'include'};
fetch('http://localhost:8080/samplelink', obj)
    .then(response => {
        if (response.status !== 200) {
            throw Error(response.status);
        }return response;})
    .then((response) => response.json())
    .then(resourceLookup => {
        var arr = [];
        var length = resourceLookup.resourceLookup.length;
        for(var i = 0 ; i< length ; i++){
            arr.push(resourceLookup.resourceLookup[i]);}
        dispatch({
            type: C.FETCH_LIST_REPORTS,
            reports: arr})}).
catch(error => {
    console.log("There was this  error" + error);});}

export const fetchReportDownloadable = (uri) => dispatch => {
var obj = {
    method: 'GET',
    headers: {
        'Authorization': 'Basic ***=',
        'Accept': 'application/json'
    },
    'credentials': 'include'
};
fetch(uri, obj)
    .then(response => {
        if (response.status !== 200) {
            throw Error(response.status);
        }
        return response ;
    })
    .then((response) => response)
    .then(resourceLookup => {
        console.log(`resourceLookup URL: ${resourceLookup.url}`)
        dispatch({
            type: C.FETCH_DOWNLOADABLE,
            report_uri: resourceLookup.url
        })
    }).
catch(error => {
    console.log("There was this  error" + error);
});}

Reducers.js

import C from '../constants'
import { combineReducers } from 'redux'
export const links = (state=null, action) =>
(action.type === C.FETCH_DOWNLOADABLE) ?
    action.report_uri :
    state
export const reports = (state=[], action) => {
switch(action.type) {
    case C.FETCH_LIST_REPORTS :
        return action.reports
    default :
        return state
}}
const rootReducer = combineReducers({
reports,
links
})
export default rootReducer

Solution

  • I will try explaining what is happening.

    First, the first problem you have is that your passing a wrong value, or either undefined value of uri in:

    <li>
      <SampleChild key={report.id} label={report.label}
         uri={this.fetchDownloadLink("http://localhost:8080/sample"+report.uri+".pdf")}
                            />
    </li>
    

    Here uri is a function, that triggers on the first render, it dispatches the fetchDownloadable(uri) action, it does not return any value however. Hence the uri is undefined.

    Secondly, you have C.FETCH_REPORT_DOWNLOADABLE constant used in your reducer. However you never dispatch an action of that type, the action dispatches C.FETCHING_DOWNLOADABLE. Hence the reducer does not do anything really, so the state does not change. My second comment was about C.FETCH_LIST_REPORTS, which is irrelevant for you right now, so I was wrong about that.

    What I would do, is create the download link from the server side. Send it back with the report object. This way, you won't need to dispatch two actions to list your reports. Then, I will dispatch my action in componentWillMount(), once it's done fetching the data, the state will be changed - if, again, you have dispatched the correct action - and you will have your reports with the download URL in the same object.

    Update

    Okay I think that I understand now. What I would do then is to send the uri as string to SampleChild, and then when it mounts I will trigger the fetchDownloadablefunction.

    SampleParent

    import React, {Component} from 'react'
    import { connect } from 'react-redux'
    import { fetchNames, fetchReportDownloadable } from '../../actions/actions'
    import SampleChild from '../ui/SampleChild'
    class SampleParent extends Component {
    constructor(props) {
        super(props);
        this.fetchDownloadLink = this.fetchDownloadLink.bind(this)
    }
    componentDidMount() {
        const { dispatch } = this.props
        dispatch(fetchNames())
    }
    fetchDownloadLink(uri){
        this.props.dispatch(fetchReportDownloadable(uri))
    }
    render() {
        return (<div><ul id="myUL">
                    {this.props.reports.map((report) => (
                        <li><SampleChild
                                key={report.id}
                                label={report.label}
                     uri={"http://localhost:8080/sample"+report.uri+".pdf"}
                     download={this.fetchDownloadLink}
                        /></li>))}
                </ul></div>
        )}
    }
    function mapStateToProps(state) {
    const { reports } = state
    return {
        reports
    }}
    export default connect(mapStateToProps)(SampleParent)
    

    SampleChild

    import React, { Component } from 'react'
    export default class SampleChild extends Component {
    componentDidMount() {
        this.props.download(this.props.uri);
    }
    render() {
        const { key, label, uri } = this.props
        return (
        <div className="inside_SampleChild" id={label}>
            {label}
            <a href={uri}><img src="../images/pdf-file_128.png" height="25px" width="25px"></img></a>
        </div>
        )}}
    

    What is supposed to happen now is that you will fetch the reports first in SampleParent then pass the information to SampleChild. Whenever a SampleChild is mounted, it will trigger fetchDownloadable action which in returns download the uri sent to it.