4

I am trying to create custom <input type="file"> upload button with the name of the uploaded file visible on the button itself after the upload, in React. I am creating this as the component. I found it very difficult to create a codepen demo so I am just uploading the code here (sorry for that).

import React, { Component, PropTypes } from 'react';
import './InputFile.css';

export default class InputFile extends Component {

constructor(props: any)
{
    super(props);
    this.getUploadedFileName = this.getUploadedFileName.bind(this);
}

getUploadedFileName(selectorFiles: FileList, props) {

const { id } = this.props;

;( function ( document, window, index )
{
    var inputs = document.querySelectorAll(`#${id}`);
    Array.prototype.forEach.call( inputs, function( input )
    {
        var label    = input.nextElementSibling,
            labelVal = label.innerHTML;

        input.addEventListener( 'change', function( e )
        {
            var fileName = '';
            if( this.files && this.files.length > 1 )
                fileName = ( this.getAttribute( 'data-multiple-caption' ) || 
'' ).replace( '{count}', this.files.length );
            else
                fileName = e.target.value.split( '\\' ).pop();

            if( fileName )
                label.querySelector( 'span' ).innerHTML = fileName;
            else
                label.innerHTML = labelVal;
        });

        // Firefox bug fix
        input.addEventListener( 'focus', function(){ input.classList.add( 
'has-focus' ); });
        input.addEventListener( 'blur', function(){ input.classList.remove( 
'has-focus' ); });
    });
}( document, window, 0 ));
}


render () {

    const { id, text, multiple } = this.props;

    return(
        <div>
            <input id={id} type="file" className="km-btn-file" data-multiple-caption="{count} files selected" multiple={multiple} onChange={ (e, id) => this.getUploadedFileName(e.target.files, id)}></input>
            <label htmlFor={id} className="km-button km-button--primary km-btn-file-label">
                <span>{text}</span>
            </label>
        </div>
    );
}
}

InputFile.propTypes = {
    id: PropTypes.string.isRequired,
    text: PropTypes.string.isRequired,
    multiple: PropTypes.string,
};

I am importing this component in my other file <InputFile id={'input-file'} text={'Upload File'} multiple={'multiple'}/>

Here is the CSS code

.km-button--primary {
    background-color: #5C5AA7;
    color: #FFFFFF;
}
.km-button {
    border-radius: 3px;
    -webkit-appearance: none;
    border: none;
    outline: none;
    background: transparent;
    height: 36px;
    padding: 0px 16px;
    margin: 0;
    font-size: 14px;
    font-weight: 400;
    text-align: center;
    min-width: 70px;
    transition: all 0.3s ease-out;
}
.km-btn-file {
    width: 0.1px;
      height: 0.1px;
      opacity: 0;
      overflow: hidden;
      position: absolute;
      z-index: -1;
  }
  .km-btn-file-label {
    line-height: 36px;
    cursor: pointer;
  }

The problem I am facing is when I click on the button first time and choose a file to upload it selects the file but does not update the text "Upload File" with the name of the file. But after the click it the second time it works fine. I don't know why that is happening and for that I need help.

Thanks.

1
  • You should use your lifecycle and state so it updates it directly. Commented Mar 29, 2018 at 12:47

2 Answers 2

3

You can use the component 'state' to update your elements.

constructor(props: any)
{
  super(props);
  this.state = {message:'some initial message'};
}

and for the onChange event do:

getUploadedFileName = (e) => {
   let files = e.target.files,
       value = e.target.value,
       message;
   if( files && files.length > 1 ) message = `${files.length} files selected`;
   else                            message = value.split( '\\' ).pop();

   if(message) this.setState({...this.state,message});
}

and then in the element just bind the value to the state:

<div>
   <input id={id} type="file" className="km-btn-file" 
      data-multiple-caption={this.state.message}
      multiple={multiple} 
      onChange={this.getUploadedFileName}>
   </input>
   <label htmlFor={id} className="km-button km-button--primary km-btn-file-label">
       <span>{text}</span>
   </label>
</div>
Sign up to request clarification or add additional context in comments.

1 Comment

I that works great. One minor correction though, I think in that last part <span>{text}</span> should be <span>{this.state.message}</span>. Unless I'm missing something.
0

You'll need to bind the text property from props to your state, so in your constructor you'll have to do;

this.state = {...props};

or

this.state = { text: props.text, id: props.id, multiple: props.multiple };

Then calling when you want to update the view value instead of manually setting the innerHtml on the label yourself; this.setState({text : new value});

And in your render method;

const { id, text, multiple } = this.state;

What this does is when you call this.setState, it tells React to re-render your component which then takes the updated values from the state.

1 Comment

It is still behaving in the same way. U have to select files twice to make the "text" update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.