0

I am trying to to an asynchronous fetch call once my modal is opened. I need that call because It fetched images and it will take around 5 seconds for the fetch to get a response.

So the modal should show first with the data and then once the fetch is complete, it should show the fetch data also.

My problem is that at the moment when calling the function with this () => {this.fetchImages(id) it is not called. I assume it's because the function is being assigned and not called.

But when I call the function fetchImages() without the () =>, I get this error :

Invariant Violation: Minified React error #31

This is my code, irrelevant part has been removed for simplicity:

renderModal = () => {
    ...
    return (

      <Modal open={openModal != null} onClose={this.closeModal}
             little showCloseIcon={true} styles={modalStyles}>
        <div className="item">
          ...
          {() => {this.fetchImages(id)
             .then(r => console.log("Fetch result" + r))}}
        </div>
      </Modal>
    );
  }

fetchImages = async (id) =>{
    console.log("Request started")
    try{
      let myImages = null;
      if(typeof(id) !== 'undefined' && id != null) {
        myImages = await fetchImages(id);
        console.log("Images: " + myImages);
        return (
          <div>
            {Array.isArray(myImages) && myImages.length !== 0 && myImages.map((item, key) =>
              <p>Image name: {item.name}, En device name: {item.name.en_US}</p>
            )}
          </div>
        );
      }
    } catch (e) {
      console.log("Failed")
    }
  }

EDIT By changing the code as suggested by jack.benson and GalAbra, I ran into an issue where I am stuck in an endless loop. I will add the new code:

Once the page loads up the renderModal is called in render() method:

{this.renderModal()}

Then I have a button that would show the modal since modal contains a line :

<Modal open={openModal != null} onClose={this.closeModal}
         little showCloseIcon={true} styles={modalStyles}>

It is called from here:

myMethod = (task) => {
    ...

    return (
  <div {...attrs}>
      ...
      <button onClick={() => {this.showModal(documents[0])}}>{translate('show_more')} &raquo;</button>
      </div>
      }
    </div>
  </div>
);};

And the part to show the modal:

  showModal = (document) => {
    this.setState({ modalOpen: document });
  };

And now the new renderModal():

renderModal = () => {
      const doThing = async () => {
      try {
        const newImages = await downloadDeviceImages(id);
        return { data: newImages };
      } catch (e) {
        console.log("Failed")
      }
    };

    const test = async (id) => {
      const imageData = await doThing(id);
      console.log("after", imageData.data);
      this.setState({
        imageData: imageData.data
      });
    };

    if(typeof(id) !== 'undefined' && id != null) {test(id);}

    return (

      <Modal open={openModal != null} onClose={this.closeModal}
             little showCloseIcon={true} styles={modalStyles}>
        <div className="item">
          ...
      <div>
        {this.state.imageData.map((item, key) =>
          <p>Device name: {item.name}, En device name: {item.name.en_US}</p>
        )}
      </div>
      </Modal>
    );
  }

The main part here is that the button will be clicked multiple times and it might have different ID on every click so a new request should be made every time.

0

2 Answers 2

2

So, this.fetchImages(id) returns a Promise, which React has no idea how to render. That is why you get the error. When you wrap it in () => { ... } you are actually creating a function that wraps your promise, but you are never calling the function. That is why it is not called. It is like saying:

function aThingToDo() {
  console.log('Did thing!')
}

function doThing() {
  aThingToDo();
}

Notice that I declared to functions, and neither of them were called. You need to call the function by explicitly adding parentheses after it (like doThing()).

That explains your error and the fact that your function is not called. Now, how to handle it. You want to wrap your fetching in a useEffect (as async stuff during render should be done). Then, if you want the data during render you can set state once it completes which will trigger a re-render. I put together a quick example of what I am talking about:

import React, { useEffect, useState } from "react";
import "./styles.css";

const doThing = async () => {
  console.log("doing a thing");
  return { data: "My data" };
};

export default function App() {
  const [data, setData] = useState("");

  useEffect(() => {
    const test = async () => {
      const data = await doThing();
      setData(data);
    };
    test();
  }, []);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      {data && data.data}
    </div>
  );
}

Here is a similar example using class components.

import React from "react";
import "./styles.css";

const doThing = async () => {
  console.log("doing a thing");
  return { message: "New message" };
};

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: "Default message"
    };
  }

  componentDidMount() {
    const test = async () => {
      const data = await doThing();
      console.log("after", data.message);
      this.setState({
        data: data.message
      });
    };
    test();
  }

  render() {
    console.log("rendering", this.state.data);
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        {this.state.data}
      </div>
    );
  }
}

Hope that helps!

Sign up to request clarification or add additional context in comments.

9 Comments

Thank you for adding the version with classes. For me it goes into an endless loop at the moment, printing out "After" for ever. Trying to find the reason. Maybe you happen to know why?
I'm not sure what you mean. I just checked the example and it isn't endlessly looping. Are you talking about the code you are working on? Are you setting state in the render method? That would cause a re-render, which would make an endless loop. I would need more information to help further.
I edited the question but it seems that yes I am setting the state in render method. Will try to do it outside of the method. Also thank you a lot for your time.
There are generally two places that setting state routinely happens: in non-render life-cycle methods (usually componentDidMount) or in event handlers (like onClick). If you need to set state as the result of an event or query that occurs when the component first loads, use componentDidMount. Otherwise, if you need to react to a user event then put your state-setting code in the event handler. Good luck!
Without seeing your exact code it is hard to say. However, based on the snippets in your question I have pieced together an example that I think should work for you: codesandbox.io/s/sweet-grass-erqcx?file=/src/App.js
|
1

Currently your code contains an async function mixed up with HTML elements.

Instead you have React's state that'll take care of the re-rendering once the data is fetched:

const ModalWrapper = ({ id }) => {
    const [myImages, setMyImages] = React.useState([]);

    const fetchImages = async (id) => {
      try {
        const newImages = await fetchImagesCall(id);
        setMyImages(newImages); // Will trigger a re-render of the component, with the new images
      } catch (e) {
        console.log("Failed")
      }
    }

    React.useEffect(() => {
        fetchImages(id);
    }, []); // This empty array makes sure the `fetchImages` call will occur only once the component is mounted

    return (
      <Modal>
        <div className="item">
          <div>
            {myImages.map((item, key) =>
              <p>Image name: {item.name}, En device name: {item.name.en_US}</p>
            )}
          </div>
        </div>
      </Modal>
    );
}

7 Comments

At the moment the code works like this: Modal is loaded in for the first time but not shown since it has no data. When a button of a specific "item" is clicked, the Modal is shown and it should make a request with that specific ID. Then the Modal can be closed and reopened with a different ID. Will this also work in this case?
You'll need to add the modal's open/close statue and take care of the id that should be transferred as a prop. Other than that, the ModalWrapper should handle exclusively to the data fetching and presenting
Is there a way to do the same thing without Hooks? Because I am using React 16.2
If you are using class-based components you'll need to use lifecycle methods like componentDidMount and class state. Here is an example based on the code from my answer: codesandbox.io/s/clever-volhard-bxewi?file=/src/App.js
@kataroty You'll need to use a class component, state and call fetchImages inside componentDidMount
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.