0

Given I have a HOC component wrapping a child component. Here is my HOC component:

import React, { Component } from 'react'
import { Put } from '../../../utils/axios'

const HOC = ( WrappedComponent ) => {
  class WithHOC extends Component {
    state = {
      loading: false,      
      userPhoto: {
        id: '',
        content: '',
      }
    }

    load = param => this.setState({ loading: param })    

    onChangeUserPhotoHOC = ( key, val ) => {
      this.setState({ [ key ]: val }, () => {
        console.log(this.state)
      })   
    }  

    updateUserPhoto = ( data ) => {          
      Put(
        `users/${ data.id }/upload_user_photo`,
        data,
        this.updateUserPhotoSuccess,
        this.updateUserPhotoError,
        this.load
      )
    } 
    updateUserPhotoSuccess = () => {
      console.log('success')
    }
    updateUserPhotoError = () => {
      console.log('error')
    }

    render = () => {
      return (
        <>
          <WrappedComponent
            { ...this.props }
            userPhoto={ this.state.userPhoto }
            onChangeUserPhotoHOC={ this.onChangeUserPhotoHOC }
            updateUserPhoto={ this.updateUserPhoto }
          />          
        </>
      )
    }
  }
  return WithHOC
}

export default HOC

And here is my child component which is responsible to take an image and convert it to base64 string.

import React from 'react'
import Filepond from '../../components/Filepond'
import { compose } from 'redux'
import _ from 'lodash'

import WithUserPhotoUploader from './actions'

const UserPhotoUploader = ({
  onChangeUserPhotoHOC,
  updateUserPhoto,
  userPhoto,
  user,
}) => {
  const updateUserPhotoData = ( key, val ) => {    
    if ( !_.isEmpty( userPhoto ) ) {
      let tmp = _.cloneDeep( userPhoto )
      tmp[ key ] = val
      tmp[ 'id' ] = user.id          
      onChangeUserPhotoHOC( 'userPhoto', tmp )      
    }    
  }

  const getBase64String = async ( items ) => {   
    if ( items && !_.isEmpty( items ) ) { 
      let item = items[ 0 ]      
      return 'a random base 64 string'      
    }
    return ''
  }
  
  return (    
    <Filepond 
      onupdatefiles={ async (items) => {
        let item = await getBase64String( items )
        await updateUserPhotoData( 'content', item )
        console.log(userPhoto)
        updateUserPhoto( userPhoto )                  
      }} />
  )
}

export default compose(
  WithUserPhotoUploader
)( UserPhotoUploader ) 

When I drop a file in the filepond component, updateUserPhotoData is called which calls the onChangeUserPhotoHOC which updates the state of the HOC component.

My question is, why wouldnt userPhoto props in the child component be updated with the new state when I drop a file? Its still showing:

{
   userId: '',
   content: '',
}

even though the HOC component state is updated. Am I missing something?

Edit 1

I tried this but it doesnt work..

return ( 
    <>      
      <Filepond         
        onaddfile={ items => {
          let item = getBase64String( items )   
          let tmp = _.cloneDeep( userPhoto ) 
          tmp[ 'content' ] = item   
          console.log(tmp)            
          Promise.all([
            onChangeUserPhotoHOC( 'userPhoto', tmp )
          ]).then(() => {
            console.log(userPhoto)
          })                        
        }}/>      
    </> 
  )  

What im trying to achieve is get the state updated first then do something with it after. I dont want to use a callback function after setting state as I prefer to call the action outside independently

Nigel
  • 985
  • 1
  • 11
  • 16
  • `updateUserPhotoData` isn't an `async` function nor does it return a Promise to `await` on. `onChangeUserPhotoHOC` simply updates state in the HOC. React state updates are asynchronous so when console logging `userPhoto` in the `onupdatefiles` callback it is logging the state from the ***current*** render cycle. You could probably just invoke `updateUserPhoto` in the HOC directly from `onChangeUserPhotoHOC` or from `componentDidUpdate` after the state updates. – Drew Reese Dec 05 '20 at 10:54
  • @DrewReese I tried another way but it doesnt seem to work as well. Would you mind explaining why my edit 1 case won't work? – Nigel Dec 05 '20 at 11:55
  • It doesn't work for all the same reasons outlined in my first comment. React state updates simply cannot be awaited on. Think of the render cycle as fenceposts. All enqueued state updates from one render cycle are processed when the next post is hit, updated for the next fence section. React state is const between the posts. Use the component lifecycles to your advantage, it's what they exist for. Queue your state update, and when it updates and the component rerenders, do any side-effect for the updated state in `componentDidUpdate`. If you want I can provide an answer so you see what I mean. – Drew Reese Dec 05 '20 at 21:59

0 Answers0