问题
I'm not a frontend developer but I know JavaScript well enough for this problem. However, I started working with Ionic lately in combination with the React framework. I'm a beginner with Ionic/React and learning it was fun until I faced this issue.
The app I'm developing is simple. I'm receiving values in (near) real time over websockets from the server and I want to show them in the UI. For this, I have ItemSliding or SlidingComponents in the UI, where I can swipe right in order to subscribe (send a 'get-message' event to the server and start consuming messages/values) over websocket or I can swipe right to unsubscribe and stop getting messages or I can click to delete the ItemSliding from the UI.
Since I want the creation of the ItemSlidings or SlidingComponents to be dynamic, I searched and found that I can loop through a list in the UI and create elements on the fly, which is a great feature and I love it so I'm using this code:
import React, { useEffect, useState } from 'react';
const [selectedTopics, setSelectedTopics] = useState([]);
.
.
.
{selectedTopics.map((topic: any, idx: number) =>
< Slider key={idx} // I tried uuid here but it didn't work
topic={topic}
socket={socket}
selectedTopics={selectedTopics}
setSelectedTopics={(topics: any) => setSelectedTopics(topics)}
/>)
}
I'm not showing all codebase because I already have thousands lines of code so I'm keeping it simple. As you can see, I'm looping over the selectedTopics list and creating a Slider (ItemSliding component) for each topic inside that list. The Slider is a custom react component I'm creating and here I will post a minimal example of the structure:
import React, { useState, useRef, useEffect} from 'react';
import { IonItemSliding, IonItemOptions, IonItemOption, IonItem, IonLabel, IonNote, IonIcon, IonSpinner } from '@ionic/react';
import { trash, ellipse } from 'ionicons/icons';
const Slider: React.FC<{
socket: SocketIOClient.Socket;
topic: any;
selectedTopics: any;
setSelectedTopics: any;
}> = (props) => {
const [realTimeValue, setRealTimeValue] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [subscribed, setSubscribed] = useState<boolean>(false);
const onSubscribe = (topic: any) => {
props.socket.on("onSubscribe", (data: any) => {
try {
const name = data.Name;
const payload = data.Value;
console.log(`received response with name: ${name} and payload: ${payload}`);
if (topic.name === name) {
console.log(`topic received= ${name} correspond to tpic name = ${topic.name}`);
setSubscribed(true);
setRealTimeValue(payload);
setLoading(false);
}
else {
console.log(`received topic = ${name} but current was ${topic.name} `);
}
}
catch(err) {
console.log("error", err);
}
});
}
const subscribe = (topic: any) => {
const req = {
subscribe: topic.Topic,
}
props.socket.emit('subscribe', req);
setLoading(true);
onSubscribe(topic);
}
const unsubscribe = (topic: any) => {
const req = {
unsubscribe: topic.Topic,
}
props.socket.emit('unsubscribe', req);
setSubscribed(false);
setRealTimeValue("");
setLoading(false);
}
const deleteTopic = (topic: any) => {
console.log('delete this topic : ' + topic.name);
unsubscribe(topic);
const newSelectedTopics = props.selectedTopics.filter((topicObj: any) => topic.name !== topicObj.Name);
props.setSelectedTopics(newSelectedTopics);
}
return (
<IonItemSliding>
<IonItemOptions onIonSwipe={() => subscribe(props.topic)} side="start">
</IonItemOptions>
<IonItem color={"light"}>
<IonLabel>
<IonIcon className="subscription-icon"
color={loading ? "warning" : subscribed ? "success" : "danger"}
icon={ellipse}
>
</IonIcon>
{props.topic.name}
</IonLabel>
<IonNote slot="end" color="dark">
{subscribed ? realTimeValue : loading ? <IonSpinner name="circles" /> : ""}
</IonNote>
</IonItem>
<IonItemOptions onIonSwipe={() => unsubscribe(props.topic)} side="end">
<IonItemOption color="danger" onClick={() => { deleteTopic(props.topic) }}>
<IonIcon icon={trash}></IonIcon>
</IonItemOption>
</IonItemOptions>
</IonItemSliding>
);
};
export default Slider;
That is the code I'm using for the Slider. So I'm looping through the selectedTopics list and I'm creating a Slider for each topic in that list. I'm passing the socket as a prop as you can see because I want to emit events and subscribe to responses from server using only one global socket connection for the app.
As I said, the problem is, sometimes I receive values and see in the console.log (in the browser) that the values came but they are not shown in the UI. Other times the values are shown but they are not in the place where I want them to be. For example, I receive a value for topic1 (which should be the first Slider) but it is shown in the second Slider, which doesn't make sense because I'm looping and creating a unique Slider for each topic in the list, right?
I tried to assign a UUID instead of a index as a key but it didn't work (in fact it was worse). I then tried to use useEffect but It was mind blowing and it didn't work too.
Am I doing something wrong here? Is my approach or algorithm wrong? I'm not sure of my way of thinking in this case as I'm a beginner in react & frontend in general. Should each slider have its own topic state and not use the props.topic at all? Should I use useEffect here?
来源:https://stackoverflow.com/questions/64050526/what-is-the-proper-way-to-create-ui-elements-dynamically-that-have-their-indepen