0

This is a React/Redux app. I have two components. One nested in the other.

<UserReview>
  <UserReviewItem>
</UserReview>

I am working with two APIs. I call one API to get a 'movieId', I use the 'movieId' to call a second API to retrieve an image. I am mapping over an array, but it seems like it is only returning the last element's movieId.

The wrapping component:

    class UserReview extends Component {
      componentDidMount() {
        this.props.fetchAllReviews();
      }

      render() {
        const allReviews = this.props.reviews.slice(-2).map((review, i) => {
          return (
            <UserReviewItem
              username={review.username}
              text={review.text}
              key={review._id}
              movieId={review.movieId}
            />
          )
        });
const mapStateToProps = state => ({
  reviews: state.movies.reviews
})

Child Component:

    class UserReviewItem extends Component {
        componentDidMount() {
            **this.props.fetchImage(this.props.movieId)**
        }
        render() {
            return (
                <div key={this.props.key}>
                    <img
                        src={`https://image.tmdb.org/t/p/original/${this.props.img}`}
                    />
                    <div>
                        <h4>{this.props.username}</h4>
                        <p>{this.props.text}</p>
                    </div>
                </div>
            );
const mapStateToProps = state => ({
    img: state.movies.img
})

I want a different image for every item in the array but I am getting the same image even though the usernames and texts are different.

A solution I tried but got the same result:

class UserReview extends Component {
  componentDidMount() {
    this.props.fetchAllReviews();
  }

  render() {
    const allReviews = this.props.reviews.slice(-2).map((review, i) => {
      return (
        <UserReviewItem
          username={review.username}
          text={review.text}
          key={review._id}
 -------> movieId={this.props.reviews[i].movieId} <--------
        />
      )
    });

const mapStateToProps = state => ({
  reviews: state.movies.reviews
})
j_nguyen
  • 119
  • 2
  • 11
  • 1
    Your UserReviewItem Component will be mounted only once when its loaded initially. THe problem is why are you trying to make n+1 queries to your backend if you can return the image of the user along with the fetchAllReviews API. like user_image : 'some_image_URL'. Why cant you do that? – GRS Jan 13 '20 at 07:07
  • Also Where are you assigning image src? Which in your case should be prop sent in UserReviewItem: `img` – Mobeen Jan 13 '20 at 07:16
  • @Mobeen he is trying to fetchImage from the moveId that is sent in the props in COmponentDidMount which wont work iteratively. He is using this.props.img in the Image url if you check. – GRS Jan 13 '20 at 07:18
  • @Lonewolf But OP isnt setting that value anywhere – Mobeen Jan 13 '20 at 07:19
  • As far as I know, `componentDidMount` in the child will call before the parent, do your api really calling with the right sequence o.o? `fetchImage` should call before `fetchAllReviews`, please correct me if I'm wrong.. – Andus Jan 13 '20 at 07:26
  • @Mobeen it's in the mapStateToProps. I accidentally left it out when I pasted the code. I edited the post. – j_nguyen Jan 13 '20 at 09:54
  • Reviews and Images live on different databases `fetchAllReviews` holds the movieId so it needs to run before `fetchImage`. – j_nguyen Jan 13 '20 at 09:57
  • So you want a solution with this approach itself ? Even though you have it in different databases you can fetch it in your API backend itself right? is it in separate service or separate database? – GRS Jan 13 '20 at 09:58
  • @Lonewolf the images come from the 'MovieDB' API. The reviews are from my own database. – j_nguyen Jan 13 '20 at 10:06
  • Its not a good approach to have API calls for all the user reviews while rendering. So in your backend before returning the fetchAllReviews APi response, iterate through it and invoke the API to get the image from MovieDB and append it int he response with some key like - image_url. Can you do that ? If you can do that i can answer with you a code that will work with this appraoch – GRS Jan 13 '20 at 10:08
  • Having code somewhere like on https://codesandbox.io where we could visualize and try to tinker with things would go a long way in this – jackthedev Jan 13 '20 at 10:19
  • @j_nguyen, That's not the correct way to do it. You are maintaining only ONE variable for img and you are using it for all UserReviewItem components. AS: REDUX is only a Global state with singleton instance. – Mobeen Jan 13 '20 at 10:30
  • The current Wrong way of doing it would be to maintain HASH with movieID as keys in Redux. BUT the preferable way would be to pass it as props as you as passing movieID. However, you would also have to change compnentDidMount to componentWillRecieveProps – Mobeen Jan 13 '20 at 10:32
  • movieId={this.props.reviews[i].movieId} -- this line seems a little tricky, you may try movieId={review.movieId} instead, as in your map function the review is already giving you the currentValue. did you check in the console if the movieId value you pass now as prop is correct? – gianni Jan 13 '20 at 22:50
  • @gianni I tried that - same result. Using Dev Tools I can see each movieId being passed correctly and rendering the correct image but then the movieId gets overridden by the next movieId in the array so the result is 3 of the same images. – j_nguyen Jan 14 '20 at 02:07
  • alright, check my answer, and see if that works out – gianni Jan 14 '20 at 10:25

2 Answers2

0

You can try this way:

class UserReview extends Component {
      componentDidMount() {
        this.props.fetchAllReviews();
      }
      
      renderItems(){
      const { reviews } = this.props
      if (!reviews) return []
      return reviews.map(review => <UserReviewItem
              username={review.username}
              text={review.text}
              key={review._id}
              movieId={review.movieId}
            />)
      }

     

      render() {        
          return (          
            this.props.reviews
             ? <div>{this.renderItems()}</div>
             : <p>Loading...</p>
          )
        };

    const mapStateToProps = state => ({
      reviews: state.movies.reviews
    })
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

basically in the renderItems function you destructure the props, get the reviews and map them. In your render function you set a loader if the views are not ready yet (you can use a loading prop if you are setting that up in your store), or call the list if the reviews are already fetched and ready.

gianni
  • 1,199
  • 2
  • 14
  • 28
  • It's the same result. All 3 components display the same images b/c they all end up with the same movieId. Here is what is happening... 1) `fetchAllReviews` is called and retrieves `movieId` 2) `movieId` is used as a parameter in the `fetchImage` function to make a call to another API where the images are 3) As a result, a url path is returned '/VsrImTfWnDjZ.jpg' 4) The 3 components that should be getting DIFFERENT images end up getting the same image – j_nguyen Jan 15 '20 at 03:19
  • ok so, all the IDs are correct, the URLs returned for each link are correct, and the image files are all correct? right? Did you try to copy and paste in another browser tab each single URL you get from the api and see if the images are displayed correctly? May be a cache problem if you previously stored a "placeholder image"? – gianni Jan 15 '20 at 10:41
0

I found the answer. Because the second call depends on information from the first call I have to chain the calls using .then()

using a fetch inside another fetch in javascript

j_nguyen
  • 119
  • 2
  • 11