I am developing a Top10UserList Component in React that fetches the top 10 users from the api, then fetches the image for each of those users and creates an image tag for each user sucessfully, and is supposed to display each user's image in their own tag.
Unfortunately though, every one of the images shares the same state, so they all flash quickly through all 10 images and then stay on the final image.
I need them to be seperate and but I am not sure how I can refactor my code to achieve this.
React Component:
const TOP_USERS_URL = 'http://localhost:8080/users/top-players'
const TOP_TEN_USERS_URL = 'http://localhost:8080/users/top-10-players'
const TOP_TEN_USERS_IMAGES_URL = 'http://localhost:8080/image-files/download-using-username'
const USER_IMG_URL = 'http://localhost:8080/image-files/'
class TopUserList extends Component {
constructor(props) {
super(props);
this.state = { users: [], top10Users: [], token: '', top10UsersImages: [], profUrl: '', profileImage: '' };
}
componentDidMount() {
fetch(TOP_USERS_URL, {
method: 'GET',
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": localStorage.getItem("token")
},
})
.then(response => response.json())
.then(data => this.setState({ users: data }));
fetch(TOP_TEN_USERS_URL, {
method: 'GET',
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": localStorage.getItem("token")
},
})
.then(response => response.json())
.then(data => {
this.setState({ top10Users: data });
console.log(data);
console.log("Data[0]")
console.log(data[0].username)
data.forEach(element => {
const imgUrl = USER_IMG_URL element.username;
const id = element.id;
fetch(imgUrl, {
method: 'GET',
headers: {
"Authorization": localStorage.getItem("token")
}
})
.then(r => r.arrayBuffer())
.then(buffer => {
const blob = new Blob([buffer]);
this.state.img = URL.createObjectURL(blob);
});
const fetchImage = async (url) => {
try {
const response = await fetch(url, {
headers: {
"Authorization": localStorage.getItem("token")
}
});
const imageBytes = await response.arrayBuffer();
var blob = new Blob([imageBytes], { type: "image/jpeg" });
var imageUrl = URL.createObjectURL(blob);
return imageUrl;
} catch (error) {
console.log("ERROR:", error);
}
};
const profileUrl = "profile-image-" id;
this.state.profUrl = profileUrl;
(async () => {
const imageSrc = await fetchImage(imgUrl);
console.log("ImageSRC:");
console.log(imageSrc);
console.log("Profile Url:");
console.log(profileUrl);
this.setState({ profileImage: imageSrc })
})();
console.log("Profile Url:");
console.log(profileUrl);
document.getElementById("app").innerHTML = `
<image id={profileUrl}>
`;
}
);
});
}
render() {
const { users, top10Users, isLoading, profileUrl, profileImage} = this.state;
console.log("Top 10 users data: ")
console.log(top10Users);
console.log("Top 10 users using index: ");
console.log(top10Users[0]);
console.log("ProfUrl: ");
console.log(profileUrl);
console.log("profImg");
console.log(profileImage);
if (isLoading) {
return <p>Loading...</p>;
}
const topUserList = users.map(user => {
return <tr key={user.id}>
<td style={{ whiteSpace: 'nowrap' }} >{user.name}</td>
<td >{user.username}</td>
<td >{user.location}</td>
</tr>
});
const topTenUserList = top10Users.map(user => {
return <div className="user-container" key={user.id}>
<Card>
<div id="app"></div>
<img src={profileImage} style={{ width: 200, height: 200 }} />
<CardTitle>{user.name}</CardTitle>
</Card>
</div>
})
return (
<div className="outer-div-container">
<div>
<Container fluid>
<h3 className="player-list-header">Top Players</h3>
<Table className="mt-4">
<thead>
<tr id="player-list-row">
<th className="player-list-data-text">Name</th>
<th className="player-list-data-text">Username</th>
<th className="player-list-data-text">Location</th>
</tr>
</thead>
<tbody>
{topUserList}
</tbody>
</Table>
{topTenUserList}
</Container>
</div>
</div>
);
}
}
export default TopUserList;
And the result:
How can I do this in React? Do I need to create 10 different image state values?
Thanks for any help!
Updated code:
class TopUserList extends Component {
constructor(props) {
super(props);
this.state = { users: [], top10Users: [], token: '', top10UsersImages: [], profUrl: '', profileImage: {} };
}
componentDidMount() {
fetch(TOP_USERS_URL, {
method: 'GET',
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": localStorage.getItem("token")
},
})
.then(response => response.json())
.then(data => this.setState({ users: data }));
fetch(TOP_TEN_USERS_URL, {
method: 'GET',
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": localStorage.getItem("token")
},
})
.then(response => response.json())
.then(data => {
this.setState({ top10Users: data });
console.log(data);
console.log("Data[0]")
console.log(data[0].username)
const profileImagesData = {}
data.forEach((element, userIndex) => {
const imgUrl = USER_IMG_URL element.username;
const id = userIndex;
fetch(imgUrl, {
method: 'GET',
headers: {
"Authorization": localStorage.getItem("token")
}
})
.then(r => r.arrayBuffer())
.then(buffer => {
const blob = new Blob([buffer]);
this.state.img = URL.createObjectURL(blob);
});
const fetchImage = async (url) => {
try {
const response = await fetch(url, {
headers: {
"Authorization": localStorage.getItem("token")
}
});
const imageBytes = await response.arrayBuffer();
var blob = new Blob([imageBytes], { type: "image/jpeg" });
var imageUrl = URL.createObjectURL(blob);
profileImageData[element?.id] = imageSrc
return imageUrl;
} catch (error) {
console.log("ERROR:", error);
}
};
const profileUrl = "profile-image-" id;
this.state.profUrl = profileUrl;
(async () => {
const imageSrc = await fetchImage(imgUrl);
console.log("ImageSRC:");
console.log(imageSrc);
console.log("Profile Url:");
console.log(profileUrl);
this.setState({profileImage:{...profileImageData}})
})();
console.log("Profile Url:");
console.log(profileUrl);
document.getElementById("app").innerHTML = `
<image id={profileUrl}>
`;
}
);
});
}
render() {
const { users, top10Users, isLoading, profileUrl, profileImage} = this.state;
console.log("Top 10 users data: ")
console.log(top10Users);
console.log("Top 10 users using index: ");
console.log(top10Users[0]);
console.log("ProfUrl: ");
console.log(profileUrl);
console.log("profImg");
console.log(profileImage);
if (isLoading) {
return <p>Loading...</p>;
}
const topUserList = users.map(user => {
return <tr key={user.id}>
<td style={{ whiteSpace: 'nowrap' }} >{user.name}</td>
<td >{user.username}</td>
<td >{user.location}</td>
</tr>
});
const topTenUserList = top10Users.map(user => {
return <div className="user-container" key={user.id}>
<Card>
<div id="app"></div>
<img src={this.state.profileImage[user.id]} style={{ width: 200, height: 200 }} />
<CardTitle>{user.name}</CardTitle>
</Card>
</div>
})
return (
<div className="outer-div-container">
<div>
<Container fluid>
<h3 className="player-list-header">Top Players</h3>
<Table className="mt-4">
<thead>
<tr id="player-list-row">
<th className="player-list-data-text">Name</th>
<th className="player-list-data-text">Username</th>
<th className="player-list-data-text">Location</th>
</tr>
</thead>
<tbody>
{topUserList}
</tbody>
</Table>
{topTenUserList}
</Container>
</div>
</div>
);
}
}
export default TopUserList;
CodePudding user response:
You can achieve that by converting your profileImage state from a string value to an object where key of this object will be your user id and the value would be image url
In your code, you may need to make following changes
Your state variable
profileImage:''inside constructor should be replaced byprofileImage:{}In
componentDidMountInitialise a variableconst profileImageData = {}above this linedata.forEach(elementUnder
const imageSrc = await fetchImage(imgUrl);, once you have the response, doprofileImageData[element?.id] = imageSrc(I am assuming that you have an id variable on each of your top10User array)Replace
this.setState({ profileImage: imageSrc })withthis.setState({profileImage:{...profileImageData})Inside your top10UserList, replace
<img src={profileImage} style={{ width: 200, height: 200 }} />with<img src={this.state.profileImage[user.id]} style={{ width: 200, height: 200 }} />If you do not have an id value then you can replace
data.forEach(elementwithdata.forEach((element, userIndex)and useuserIndexas your key value instead ofelement.idand do the same in top10UserList as well

