I'm creating a React app that reads data from a text file and I want to display the processing progress.
I perform multiple data transformations in sequence (as they are dependent on each other) and I want to have a progress bar for each of the processing.
I have a progress component that works as expected but I'm not able to update the progress whilst it's happening, all bars are fully filled after the whole process finishes.
From what I can see, React is queueing all state updates and rendering everything in the end.
So, this is what I have at the moment: Analysis.js
export function Analysis ({ onError, whatsapp }) {
const [baseContent, setBaseContent] = useState(0)
const [chatContent, setChatContent] = useState(0)
const [splitMessages, setSplitMessages] = useState(0)
function onSetBaseContent (value) {
setBaseContent(value || baseContent + 1)
}
function onSetChatContent (value) {
setChatContent(value || chatContent + 1)
}
function onSplitMessages (value) {
setSplitMessages(value || splitMessages + 1)
}
const whatsappCallBacks = { onSetBaseContent, onSetChatContent, onSplitMessages }
useEffect(() => {
whatsapp.setData(whatsappCallBacks)
}, [])
return (
<>
<Row>
<p>
Analysing...
</p>
</Row>
<Row>
<Col xs={1}></Col>
<Col>
<Progress title = 'Base content' percentage={baseContent}></Progress>
<Progress title = 'Chat content' percentage={chatContent}></Progress>
<Progress title = 'Content split' percentage={splitMessages}></Progress>
</Col>
<Col xs={1}></Col>
</Row>
</>
)
}
whatsapp is an instance of the Whatsapp class created before.
The Progress component simply has a bar that goes up to 100% width, and it's tested and working.
Here is the important part of the Whatsapp class
Whatsapp.js
...
async setData ({ onSetBaseContent, onSetChatContent, onSplitMessages }) {
this.setBaseContent(onSetBaseContent)
this.setMessages(onSetChatContent, onSplitMessages)
}
setBaseContent (onSetBaseContent) {
this.content = this.file.content.replace(/\r\n/, '\n').replace(/\r/, '\n').split('\n')
onSetBaseContent(100)
}
setMessages (onSetChatContent, onSplitMessages) {
// Read each line and put them as a string entry. If the line does not match
// the messageRegEx, includes in the previus entry with a line break
let percentage = Number((this.content.length / 100).toFixed())
for (let i = 0; i < this.content.length; i++) {
if (i % percentage === 0) {
onSetChatContent()
}
const message = this.content[i]
if (message.match(this.messageRegEx)) {
this.messages.push(message)
} else {
// Join the lines that are continuation of the previus message
this.messages[i - 1] += `\n${message}`
}
}
onSetChatContent(100)
const contact = new Contact()
const replacementsFileContent = []
this.setDateFormat()
percentage = Number((this.messages.length / 100).toFixed())
let i = 0
// Replace each entry by the Message instance and remove the null entries
this.messages = this.messages
.map(m => {
if (i % percentage === 0) {
onSplitMessages()
}
i++
const split = this.contentSplitRegex.exec(m)
if (!split) {
return null
}
const date = this.dateFormat === dateFormat.DAY_MONTH_YEAR
? `${split[1]}/${split[2]}/${split[3]} ${split[4]}:${split[5]}:${split[6]}`
: `${split[2]}/${split[1]}/${split[3]} ${split[4]}:${split[5]}:${split[6]}`
let cont = Contact.clean(split[7])
const content = split[8]
const replaced = contact.replace(cont)
if (!replaced && !replacementsFileContent.find(r => r === cont)) {
replacementsFileContent.push(cont)
} else if (replaced) {
cont = replaced
}
return new Message(date, cont, content)
})
.filter(messsage => messsage != null)
onSplitMessages(100)
if (replacementsFileContent.length > 0) {
Contact.saveReplacements(replacementsFileContent)
}
if (this.messages.length === 0) {
throw new Error('Failed to read messages from the file')
}
}
Debugging the code I can see the state updates are called correctly, but the progress is only updated (up to 100) all at the same time after the whole processing finishes.
Here's a video I recorded with the result
I already tried some "techniques" to force React to update: setTimeout, setInterval, updateState, moving things inside useEffect and nothing so far worked as they should.
I'm about to give up on react and do it using vanilla JS.
Source code here.