react cannot use fileinput twice without refreshing the page - reactjs

I'm using html fileinput to upload a file with reactjs, but once I uploaded a file, I cannot call the function to upload another file, unless I refresh the page of course.
A simplified version of my code would be:
class Matrice extends React.Component {
constructor(props) {
super(props);
this.fileInput = null;
}
uploadQuestion = async e => {
console.log("uploading question");
if (e.target.files[0]) {
const form = new FormData();
let type;
if (e.target.files[0].type == "image/jpeg") type = ".jpg";
if (e.target.files[0].type == "image/png") type = ".png";
if (e.target.files[0].type == "image/gif") type = ".gif";
// const fileName = this.props.current + type;
form.append("files", e.target.files[0]); //filename
form.append("ref", "exam"); // model
form.append("refId", this.props.match.params.id); // id
form.append("field", "media"); // name of field (image field)
this.setState({ questionUploadLoading: true });
const files = await strapi.upload(form);
this.saveMontage(files, undefined, "question");
}
};
render() {
return (
<>
<input
style={{ display: "none" }}
ref={fileInput => (this.fileInput = fileInput)}
onChange={this.uploadQuestion}
className="file"
type="file"
id="imgAdd"
/>
<button
onClick={() => this.fileInput.click()}
type="button"
className="btn btn-secondary"
>
<i className="fas fa-image" />
</button>
</>
);
}
}
But my function uploadQuestion cannot be called again once I finished uploading a file. Namely, the console.log('uploading question') doesn't show up (the second time).
I don't know what could be the reason, but I guess that something is preventing the onChange handler as if, uploading a file the second time doesn't "changes" the trigger.
Does anybody have an idea what could cause this?
Thanks

You can reset the file input by setting its value to the empty string, and you will be able to use it again.
uploadQuestion = async (e) => {
console.log('uploading question')
if (e.target.files[0]) {
// ...
this.fileInput.value = "";
}
}

You need to set the state for image that to be upload there is flow the step
Set a state for upload file in your Constructor (uploadFile:null)
Add a function for handle file Change
Use state upload(uploadFile) into uploadQuestion() instead of e.target.value[0]
After Upload setState back to uploadFile:null
set the file input onChange={this.fileHandle}
class Matrice extends React.Component {
constructor(props) {
super(props);
this.state:{
uploadFile:null
}
this.fileInput = null;
this.fileHandle = this.fileHandle.bind(this)
}
fileHandle (e, a) {
e.preventDefault()
this.setState({ upload: e.target.files[0] })
};
uploadQuestion = async (e) => {
console.log('uploading question')
if (e.target.files[0]) {
const form = new FormData();
let type;
if (e.target.files[0].type == 'image/jpeg') type = '.jpg'
if (e.target.files[0].type == 'image/png') type = '.png';
if (e.target.files[0].type == 'image/gif') type = '.gif';
// const fileName = this.props.current + type;
//Use state upload(uploadFile) into uploadQuestion() instead of e.target.value[0]
file.append('images', this.state.uploadFile, this.state.uploadFile.name) //filename
form.append('ref', 'exam'); // model
form.append('refId', this.props.match.params.id) // id
form.append('field', 'media') // name of field (image field)
this.setState({questionUploadLoading: true})
const files = await strapi.upload(form);
this.saveMontage(files, undefined, 'question')
//After Upload setState back to uploadFile:null
this.setState({uploadFile:null})
}
}
if you like to valid in onChange you can modify function as Below
fileHandle (e) {
e.preventDefault()
if (!e.target.files[0].name.match(/.(jpg|jpeg|png|gif)$/i)) {
this.setState({ errorMsg: 'Please upload valid file. Allowed format jpg, jpeg, png, gif' })
return false
} else {
this.setState({ upload: e.target.files[0], errorMsg: '' })
}
};

I had a heck of a time with this and no matter what I did from above nothing worked. Now, I've simply hardcoded the value to an empty string and I can upload over and over. I'm not even sure why this works, but I don't ever need the text value. The server cares about that. Here's a styled button using Material-UI where you never see the input, but you can upload over and over (in my case the server sends back some error and please fix your xlsx file message and I needed the user to be able to fix and try again):
import React from 'react';
import { Button } from '#material-ui/core';
import BackupIcon from '#material-ui/icons/Backup';
const UploadButton = ({ onChange, name, label, disabled }) => {
return (
<div className={'MuiFormControl-root MuiTextField-root'}>
<input
name={name}
id='contained-button-file'
type='file'
accept='.csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
style={{ display: 'none' }}
onChange={onChange}
disabled={disabled}
value=''
/>
<label htmlFor='contained-button-file'>
<Button
color='primary'
aria-label='Upload scan file.'
variant='contained'
component='span'
startIcon={<BackupIcon />}
disabled={disabled}
>
{label}
</Button>
</label>
</div>
);
};
export default UploadButton;

Just handle it using click event
const handleClick = event => {
const { target = {} } = event || {};
target.value = "";
};
<input type="file" onChange={handleChange} onClick={handleClick} />

Related

input type file doesn't act independently for different children in a map in react js

I am iterating a list of file inputs in react through a map function
render() {
const {
form,
loading,
filledIndex,
} = this.state;
return (
map function
{form.a &&
form.a.map((prop, index) => (
upload component
<label
htmlFor="proofDocUrl"
className="save-form-btn-personal"
>
Upload
</label>
<input
type="file"
accept=".png,.jpg"
id="proofDocUrl"
name="proofDocUrl"
onChange={(e) => this.handleChangeUpload(e, index)}
onClick={(event) => {
event.target.value = null;
}}
/>
handleChangeUpload function. send file to server and fetch url
handleChangeUpload = async (e, index) => {
if (e.target.files) {
let formData = new FormData();
formData.append("b", e.target.files[0]);
this.setState({ loading: true });
const form = { ...this.state.form };
let documentUpload =
await A_service.A_api(formData);
if (documentUpload) {
if (documentUpload.data && documentUpload.data.data) {
documentUpload = documentUpload.data.data;
}
form.x[index][e.target.name] =
documentUpload.parameter;
}
this.setState({ loading: false, form });
}
};
states that are used. form state has x component which is iterated through a map.
proofDocUrl stores image url
form: {
x: [],
},
formLocal: {
proofDocUrl: "",
},
when a new object is added to form.x state and when it creates a new child in render the file input acts as if its the same for all childeren.
when a image is uploaded in one file component in one child, same will be uploaded for all childern.
How to make each file component in each child acts independently.

how to solve "TypeError: Cannot read properties of undefined (reading 'state') at checkGuess (Flashcard.js:34:1)" when trying to post form data?

I have a simple react flashcard app that sends data to the backend about the flashcard including the question, answer choices, and the answer that the user guessed along with the correct answer. I am trying to post the user's name that they enter into the form to the same backend route as the other data. I have successfully made the form and on submit the program alerts the user that they've entered their username and it displays the username. that works perfectly. Now I'm trying to get that value that was entered for the username and post it to the backend in the same function that I post the other data to the backend so it all gets sent together conveniently on each click. Here is my updated code for my form:
import React from "react"
export default class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.checkGuess = this.checkGuess.bind(this);
}
handleChange = (event) => {
this.setState({value: event.target.value});
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
checkGuess() {
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange=
{this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
and here is the other component that builds the flashcard and posts the data to the endpoint onClick through the checkGuess function. This already works perfectly without the new username value. :
import React, { useState, useEffect, useRef } from 'react'
import NameForm from './NameForm'
export default function Flashcard({ flashcard }) { // recieving
flashcard
prop from our mapping in flashcardlist.js, each w a unique id
const MAX_TRIES = 4
// const [incorrect, setIncorrect] = useState(incorrect)
const [guess, setGuess] = useState(0)
const [flip, setFlip] = useState(false)
const [height, setHeight] = useState('initial') //sets the state for our
initial height to be replaced by the max height
const frontEl = useRef() // lets us have a reference from the front and
back through every rerendering of them
const backEl = useRef()
// const callDouble = () =>{
// checkGuess();
// postData();
// }
async function postData() {
}
const checkGuess = (answer) => {
try {
console.log(this.state.value)
let result = fetch('http://127.0.0.1:5000/post', {
method: 'POST',
mode: 'no-cors',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
key: `${Date.now()}`,
question: flashcard.question,
answer: flashcard.answer,
options: flashcard.options,
guess: answer,
user: this.state.value
})
});
} catch(e) {
console.log(e)
}
if (answer === flashcard.answer) {
setFlip(true)
return
}
if (guess + 1 === MAX_TRIES) {
setFlip(true)
}
setGuess(guess + 1)
// setIncorrect(true)
}
function setMaxHeight() {
const frontHeight = frontEl.current.getBoundingClientRect().height
//gives us dimensions of the rectangle but we only need the height
const backHeight = backEl.current.getBoundingClientRect().height
setHeight(Math.max(frontHeight, backHeight, 100)) // sets the height
(setHeight) to the maximum height of front or back but the minimum is
100px
}
useEffect(setMaxHeight, [flashcard.question, flashcard.answer,
flashcard.options]) //anytime any of these change then the setMaxHeight
will change
useEffect(() => {
window.addEventListener('resize', setMaxHeight) //everytime we resize
our browser, it sets the max height again
return () => window.removeEventListener('resize', setMaxHeight)
//removes the eventlistener when component destroys itself
}, [])
return (
<div
onClick={() => postData()}
className={`card ${flip ? 'flip' : ''}`} // if flip is true classname
will be card and flip, if flip isnt true it will just be card
style={{ height: height }} //setting height to the variable height
// onClick={() => setFlip(!flip)} // click changes it from flip to non
flip
>
<div className="front" ref={frontEl}>
{flashcard.question}
<div className='flashcard-options'>
{flashcard.options.map(option => {
return <div key={option} onClick={() =>
checkGuess(option)} className='flashcard-option'>{option}</div>
})}
</div>
</div>
<div onClick={() => setFlip(!flip)} className='back' ref={backEl}>
{flashcard.answer}
</div>
</div>
)
}
// setting the front to show the question and the answers by looping
through the options to make them each an option with a class name to style
// back shows the answer
and this is the new error:
TypeError: Cannot read properties of undefined (reading 'state')
at checkGuess (Flashcard.js:34:1)
at onClick (Flashcard.js:92:1)
Since the question has changed a bit based on new code shared by you, please remove the following line:
this.checkGuess = this.checkGuess.bind(this);
Now React components work in a tree structure Root->Children
So you need to figure out whether Flashcard is the root, calling NameForm. Or the other way round.
If Flashcard calls NameForm:
Call NameForm as follows while passing in the checkGuess function:
<NameForm answer={flashcard.answer} checkGuess={checkGuess} />
Then inside NameForm:
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
this.props.checkGuess(this.props.answer, this.state.value);
event.preventDefault();
}
To support this in Flashcard, change the function signature of checkGuess to:
const checkGuess = (answer, stateValue) => { ... }
And here you use stateValue instead of this.state.value

Adding file to a list that has already previously been deleted in React

I have a custom FileInput class in my React code. Now If I add a File with this FileInput to an Array, then remove the File from that List and then try to add the same File again, it is no longer being added to the Array.
FileInput.js
let FileInput = (props) => {
const { className, children } = props;
let attributes = {
...props,
type: 'file',
className: 'form-input-file',
};
delete attributes.children;
return (
<label className={'form-input-file-parent form-input-clickable' + (className ? ' ' + className : '')}>
<input {...attributes} />
{children}
</label>
)
};
export default FileInput;
And then the code where I add/remove:
<FileInput
multiple
onChange={onAttachmentsAdded}
>
Add File
</FileInput>
onAttachmentsAdded = (evt) => {
this.setState({
attachments: [
...attachments,
...evt.target.files,
],
});
};
onAttachmentsRemove = (file) => {
const array = [...attachments];
const index = array.indexOf(file);
if (index !== -1) {
array.splice(index, 1);
this.setState({
attachments: array,
});
}
};
Created a CodeSandbox :
CodeSandbox
Add a file, remove the file then try to add the same File again.
Can someone help me figure out what am I doing wrong?
Issue here was that when selecting a new File, onChange was called, then the File was deleted and when the same File was selected again.. onChange was not called because File did not actually change, the solution was to add this line, into the FileInput.
onClick={(event) => {
event.currentTarget.value = null;
}}

Trying to use react-papaparse to stream a local file row by row but it's not working - am I coding it correctly or is it just not possible?

I can use react-papaparse to parse a local file triggering onFileLoad={this.handleOnFileLoad} ok, but I'd like to stream it, so I tried the code below, trying to pass onStep or step in props but its not triggering. The documentation implies it's possible but am I going about this the wrong way? I want to process each row at a time in case its a really big file. Thanks.
import React from 'react';
import { CSVReader } from 'react-papaparse';
const buttonRef = React.createRef();
export default class CSVReader1 extends React.Component {
handleOpenDialog = (e) => {
// Note that the ref is set async, so it might be null at some point
if (buttonRef.current) {
buttonRef.current.open(e);
}
};
handleOnStep = (row) => {
console.log('handleOnComplete---------------------------');
console.log(row);
console.log('---------------------------');
};
handleOnError = (err, file, inputElem, reason) => {
console.log('handleOnError---------------------------');
console.log(err);
console.log('---------------------------');
};
handleOnRemoveFile = (data) => {
console.log('handleOnRemoveFile---------------------------');
console.log(data);
console.log('---------------------------');
};
handleRemoveFile = (e) => {
// Note that the ref is set async, so it might be null at some point
if (buttonRef.current) {
buttonRef.current.removeFile(e);
}
};
render() {
return (
<CSVReader
ref={buttonRef}
onError={this.handleOnError}
onStep={this.handleOnStep}
noClick
noDrag
onRemoveFile={this.handleOnRemoveFile}
>
{({ file }) => (
<div className="form">
<div>
<button className="button" type="button" onClick={this.handleOpenDialog} >Browse file</button>
</div>
<div className="text-input">
{file && file.name}
</div>
<div>
<button className="button button--secondary" onClick={this.handleRemoveFile}>Remove</button>
</div>
</div>
)}
</CSVReader>
);
};
}
If you have either a step or complete handler defined, then the onFileLoad doesn't get called.
Source Line
config?.complete || config?.step
? config.complete
: () => {
if (!onDrop && onFileLoad) {
onFileLoad(data, file);
} else if (onDrop && !onFileLoad) {
onDrop(data, file);
}
}

Upload and send data with axios in reactjs

How can i get data from input with type file and send it with axios in reactjs?
I found something about formData but i didn't find anything about get data from input and send it with axios.
thanks.
Lets assume that you have all the input data along with the file in your state like
constructor(props) {
super(props);
this.state = {
file : someName.txt, // file input
stateName : 'MP' // Text Input
date : 07/08/2018 // Date input
}
}
Now, in you handelSubmit method construct a JSON Object
handelSubmit = () => {
const { file, stateName, date } = this.state;
let data = [];
data['file'] = file;
data['stateName'] = stateName;
data['date'] = date;
// a function which makes a axios call to API.
uploadFile(data, (response) => {
// your code after API response
});
}
Here is a function to make a API call by axios
uploadFile(data, callback) {
const url = ''; // url to make a request
const request = axios.post(url, data);
request.then((response) => callback(response));
request.catch((err) => callback(err.response));
}
UPDATED :
Text On Change method to set state
handelOnChange = (event) => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
}
Method on upload of file to set into state
handelOnUploadFile = (event) => {
this.setState({
file : event.target.files
})
}
Here is a JSX code.
render() {
return(
<div>
<input type="file" onChange={this.handelOnUploadFile} /> {/* input tag which to upload file */}
<input type="text" name="stateName" onChange={this.handelOnChange} /> {/* text input tag */}
<button type="submit" onClick={this.handelSubmit}> UPLOAD </button>
</div>
)
}
Hope it helps you.

Resources