Answer the question
In order to leave comments, you need to log in
Why inputRef.current=null?
There is an UploadUPApp component that receives the url of the android application from location.state.apk.
It renders the FileUploader, passes the inputRefs created with useRef() into it, and assigns it to the input.
The component passes location.state.apk and inputRef to the passURLtoInputFile function, where it adds the file by reference to input.current.files.
But instead of the expected behavior, passURLtoInputFile prints an input.current is null error to the console .
What am I doing wrong?
const initialApp = {
applicationType: 'add a text',
description: 'add a text',
versionName: '',
versionCode: '',
privacyRating: 3,
rating: 2,
permissions: [],
appSize: '',
fileAppUrl: 'add a text',
iconUrl: 'add a text',
applicationName: '',
applicationId: '',
localFileUrl: '',
localIconUrl: ''
};
const UploadUPApp = () => {
const inputRef = useRef(null);
const [iconBinaryFile, setIconBinaryFile] = useState('');
const [appBinaryFile, setAppBinaryFile] = useState('');
const [loading, setLoading] = useState(false);
const [current, setCurrent] = useState(initialApp);
const {
applicationType,
description,
versionName,
versionCode,
privacyRating,
rating,
permissions,
appSize,
applicationName,
applicationId,
localFileUrl,
localIconUrl,
} = current;
const updSpinner = isLoading => {
setLoading(isLoading);
};
const location = useLocation();
const onChange = (e) =>
setCurrent({...current, [e.target.name]: e.target.value});
const onSubmit = async (e) => {
e.preventDefault();
};
const getAnalyzedAppData = (data, appSize, fileName, binaryFile, isIcon) => {
console.log('getAnalyzedAppData');
if (isIcon) {
setIconBinaryFile(binaryFile);
setCurrent({...current, localIconUrl: fileName});
} else {
setAppBinaryFile(binaryFile);
setCurrent({
...current, applicationId: data.packageName,
applicationName: data.label,
versionName: data.versionName,
versionCode: data.versionCode,
permissions: data.usesPermissions,
appSize: setCorrectSize(appSize),
localFileUrl: fileName
});
}
};
useEffect(() => {
if (location.state && location.state.apk) {
updSpinner(true);
passURLtoInputFile(inputRef, 'apk', location.state.apk)
.then(async () => {
const fileUploaded = inputRef.current.files[0];
const fileName = fileUploaded.name;
const appSize = setCorrectSize(fileUploaded.size);
const formData = new FormData();
formData.append('file', fileUploaded);
try {
const res = await axios({
method: 'post',
url: 'api/admin/parse',
data: formData,
headers: {'Content-Type': 'multipart/form-data'},
});
getAnalyzedAppData(res.data, appSize, fileName, fileUploaded, false);
updSpinner(false);
} catch (error) {
console.log(error);
updSpinner(false);
window.alert('There was a problem to analyze application file');
}
});
}
}, [inputRef]);
if (loading) {
return <Spinner/>;
}
return (
<Fragment>
<form onSubmit={onSubmit}>
<FileUploader buttonText='Analyze Application File'
targetUrl='api/admin/parse'
getAnalyzedAppData={getAnalyzedAppData} isIcon={false}
updSpinner={updSpinner}
needToParse
externalApk={(location.state) && location.state.apk}
ref={inputRef}
/>
<FileUploader buttonText='Select Icon File'
getAnalyzedAppData={getAnalyzedAppData} isIcon
needToParse={false}
/>
</form>
</Fragment>
);
};
const FileUploader = (props) => {
// Create a reference to the hidden file input element
const {hiddenFileInput, isIcon, getAnalyzedAppData, needToParse, updSpinner, targetUrl, buttonText, <b>ref</b>} = props;
// Programmatically click the hidden file input element
// when the Button component is clicked
const handleClick = event => {
event.preventDefault();
hiddenFileInput.current.click();
};
// Call a function (passed as a prop from the parent component)
// to handle the user-selected file
const handleChange = async (event) => { // To-Do change to decoupled icon and APK file loader
console.log('handling', event)
const fileUploaded = event.target.files[0];
const fileName = fileUploaded.name;
// props.handleFile(fileUploaded);
if (isIcon)
getAnalyzedAppData(null, null, fileName, fileUploaded,true);
else if (!needToParse)
getAnalyzedAppData(null, null, fileName, fileUploaded,false);
else {
updSpinner(true);
console.log(targetUrl);
const appSize = fileUploaded.size;
const formData = new FormData();
formData.append('file', fileUploaded);
try {
const res = await axios({
method: "post",
url: targetUrl,
data: formData,
headers: { "Content-Type": "multipart/form-data" },
})
getAnalyzedAppData(res.data, appSize, fileName, fileUploaded,false);
updSpinner(false);
} catch (error) {
console.log(error);
updSpinner(false);
window.alert('There was a problem to analyze application file');
}
}
};
return (
<>
<button type="button" onClick={handleClick}>
{buttonText}
</button>
<input
type="file"
ref={ref}
onChange={handleChange}
style={{ display: 'none' }}
/>
</>
);
});
const passURLtoInputFile = async (input, name, url) => {
try {
const blob = await (await fetch(apkURL(url))).blob();
const dt = new DataTransfer();
dt.items.add(new File([blob], name, {type: blob.type}));
input.current.files = dt.files;
console.log('passed successfully:');
console.dir(input.current.files);
} catch (err) {
console.error('the file wasn\'t passed:');
console.error(err);
}
};
Answer the question
In order to leave comments, you need to log in
The ref must be passed using forwardRef. // update. I'm wrong
useEffect doesn't track the change of ref objects, so it will be called once on the initial render.
If you want to send the loaded file through the parent component, the best practice is to create a state in the parent component and pass the state change handler to the child.
const [state, setState] = useState(undefined);
const handleChangeState = (newState) => {
setState(newState);
}
return (
<Component handler={handleChangeState} />
);
...
function Component({handler}) {
return (
<input onChange={(e) => handler(e.target.files)} />
);
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question