React screen update from a loop process

Viewed 121

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.

0 Answers
Related