Reputation: 1117
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')} »</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.
Upvotes: 0
Views: 2953
Reputation: 2363
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!
Upvotes: 2
Reputation: 5148
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>
);
}
Upvotes: 1