2

I can't quite wrap my head around this.

I'm having to pass data that's fetched asynchronously. Issue is, the props are asynchronous as well. Here's a simplified version of the component:

import React, { Component }  from 'react'
import CSVLink from 'react-csv'
import generateData from './customApi/generateData

type Props = { job?: JobType | undefined }
type State = { csvData: string[][] }

class MyComponent extends Component<Props, State> {
  state = {
    csvData = [],
  }

  generateCsv = async (jobId: string) => {
    const csvData = await generateData(jobId)
    this.setState({ csvData })
  }

  async componentDidMount() {
    const { job } = this.props
    await this.generateCsv(job.id)
  }

  render() {
    const { csvData } = this.state

    return (
       <CSVLink data={csvData}>
          <p>Download csv</p>
       </CSVLink>
    )
  }
}

export default connectFirestore(
   (db, params) => ({ getJob(db, params.id) })
)

Basically my props are fetched from an API call to firestore, so it takes a while to load the job. Issue is, when I'm trying to pass the jobId inside the async componentDidMount(), it ends up passing undefined, because the job props are not loaded yet.

I don't link the whole passing state to async call business going on, but I can't think of any other way, how I would passing the csvData from the generateDate() async call only once the Promise is resolved.

I guess the easiest way to approach this would be, to perhaps somehow check if the props inside the componentDidMount() are already fetched?

What would be the correct way to approach this?

Samuel Hulla
  • 6,617
  • 7
  • 36
  • 70

4 Answers4

0

You are missing to implement the constructor where the props are set

constructor(props){
  super(props);
  this.state = {
     csvData = [],
  };
}

componentDidMount(){
  //This will work
  console.log(this.props.job);
};
ludwiguer
  • 2,177
  • 2
  • 15
  • 21
  • While I do agree constructor shouldn't be omitted, this will still leave `props.job` as `undefined` on the initial render – Samuel Hulla Jun 15 '20 at 21:50
0

If job property should by async, do it async: rather than passing a value which will change in future, you can pass a Promise which resolves with the id, than change your componentDidMount as follows:

componentDidMount() {
    const id = await this.props.job;
    this.generateCsv(id)
}

Hope this helps.

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
  • You wouldn't be able to call await in a synchronous `componentDidMount()`, also `await this.props.job` wouldn't do anything since it's not a function. – Samuel Hulla Jun 15 '20 at 21:49
0

Maybe you should change the code inside your parent component, I imagine that you are making the API call there, as you are passing this data as props in this component you are showing to us.

I also imagine that you are making the API call with the fetch command, which can have a .then(()=>{}) method triggered when the API call finished loading, after that you can change the state of that component carrying the API fetched data and THEN render this child. Something I used recently for my project was to load from API, update state and conditionally render the child component, which will not know I made an API call because I am passing already loaded data. Normally while it is waiting I put something like this:

if(this.state.dataFetched == null)
   return(<h1>Loading page...</h1>)
else return <childComponent loadedData = {this.state.dataFetched}/>

And then access that data as this.props.loadedData

Leandro
  • 36
  • 6
0

Not sure if it's the optimal solution, but it works.

I've decided to use componentDidUpdate() life-cycle method, where I'm comparing whether the props have already update and once they did, I'm calling the asynchornous function for generating the csv data.

async componentDidUpdate(prevProps: Props) {
  if (prevProps !== this.props && !!this.props) {
    const { job } = this.props
    if (job) {
      await this.generateCsv(job.id)
    }
  }
}

This way we generate new data every time the data inside the props.job changed and also we don't attempt to call generateCsv() on undefined while it's still being fetched from firestore.

Samuel Hulla
  • 6,617
  • 7
  • 36
  • 70