问题
I have a complete mess of a component. Right now I pass a function I have been trying a million things I can not make it work.
export default class DatafileUpload extends Component {
initialState = {
fileUploading: false,
fileList: [],
status: 'empty', // 'empty' | 'active' | 'success' | 'exception'
file: {}
}
state = this.initialState
static propTypes = {
userId: PropTypes.string.isRequired,
datasetId: PropTypes.string.isRequired
}
scrubFilename = (filename) => filename.replace(/[^\w\d_\-.]+/ig, '')
requestSignedS3Url = (file) => {
const filename = this.scrubFilename(file.name)
const params = {
userId: this.props.userId,
contentType: file.type,
Key: `${filename}`
};
return api.get('/s3/signUpload', { params })
.then(response => {
return response.data;
})
.catch(error => {
console.error(error);
});
}
uploadFile = (file) => {
this.requestSignedS3Url(file)
.then(signResult => this.uploadToS3(file, signResult))
.catch(error => console.log(error))
}
createCORSRequest = (method, url, opts) => {
opts = opts || {};
let xhr = new XMLHttpRequest();
if (xhr.withCredentials != null) {
xhr.open(method, url, true);
if (opts.withCredentials != null) {
xhr.withCredentials = opts.withCredentials;
}
} else if (typeof XDomainRequest !== "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
};
stepFunctions = () => {
return {
preprocess: (file) => {
console.log('Pre-process: ' + file.name);
},
onProgress: (percent, message, file) => {
this.setState({ fileUploading: true })
console.log('Upload progress: ' + percent + '% ' + message);
},
onFinish: (signResult) => {
this.setState({ fileUploading: false })
console.log("Upload finished: " + signResult.publicUrl)
},
onError: (message) => {
this.setState({ fileUploading: false })
console.log("Upload error: " + message);
},
scrubFilename: (filename) => {
return filename.replace(/[^\w\d_\-\.]+/ig, '');
},
onFinishS3Put: (signResult, file) => {
console.log(signResult)
return console.log('base.onFinishS3Put()', signResult.publicUrl);
}
}
}
uploadToS3 = async (file, signResult) => {
const xhr = await this.createCORSRequest('PUT', signResult.signedUrl);
const functions = this.stepFunctions()
functions.preprocess(file)
if (!xhr) {
functions.onError('CORS not supported', file);
} else {
xhr.onload = () => {
if (xhr.status === 200) {
functions.onProgress(100, 'Upload completed', file);
return functions.onFinishS3Put('potatopotato', file);
} else {
return functions.onError('Upload error: ' + xhr.status, file);
}
};
xhr.onerror = () => {
return functions.onError('XHR error', file);
};
xhr.upload.onprogress = (e) => {
let percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
}
};
}
xhr.setRequestHeader('Content-Type', file.type);
if (signResult.headers) {
const signResultHeaders = signResult.headers
Object.keys(signResultHeaders).forEach(key => {
const val = signResultHeaders[key];
xhr.setRequestHeader(key, val);
})
}
xhr.setRequestHeader('x-amz-acl', 'public-read');
this.httprequest = xhr;
return xhr.send(file);
};
handleChange = ({ file, fileList }) => {
const functions = this.stepFunctions()
functions.preprocess(file)
if (!file) {
functions.onError('CORS not supported', file);
} else {
file.onload = () => {
if (file.status === 200) {
functions.onProgress(100, 'Upload completed', file);
return functions.onFinishS3Put('potatopotato', file);
} else {
return functions.onError('Upload error: ' + file.status, file);
}
};
file.onerror = () => {
return functions.onError('XHR error', file);
};
file.upload.onprogress = (e) => {
let percentLoaded;
if (e.lengthComputable) {
percentLoaded = Math.round((e.loaded / e.total) * 100);
return functions.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
}
};
}
console.log('File: ', file)
// always setState
this.setState({ fileList });
}
render() {
const props = {
onChange: this.handleChange,
multiple: true,
name: "uploadFile",
defaultFileList: this.initialState.fileList,
data: this.uploadFile,
listType: "text",
customRequest: ????,
showUploadList: {
showPreviewIcon: true,
showRemoveIcon: true
},
onProgress: ( {percent} ) => {
this.setState({ fileUploading: true })
console.log('Upload progress: ' + percent + '% ' );
},
onError: (error, body) => {
this.setState({ fileUploading: false })
console.log("Upload error: " + error);
},
onSuccess: (body)=> {
console.log(body)
return console.log('base.onFinishS3Put()');
}
};
return (
<Upload {...props} fileList={this.state.fileList}>
<Button>
<Icon type="upload" /> Upload
</Button>
</Upload>
)
}
}
I know this code is a mess that doesn't make sense and have duplicated data all around. I want it to make it work and then clean up/optimse. Basically I am not able to make the component progress bar update nor with the onChange
nor when I am trying to use the customRequest
. When is customRequest
called? This is not very abundant in explanations... I don't understand how does it do the replacement of Ajax upload.
回答1:
I was struggling with that as well and then I found your question.
So the way I found to use customRequest and onChange is:
<Upload name="file" customRequest={this.customRequest} onChange={this.onChange}>
<Button>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
...
onChange = (info) => {
const reader = new FileReader();
reader.onloadend = (obj) => {
this.imageDataAsURL = obj.srcElement.result;
};
reader.readAsDataURL(info.file.originFileObj);
...
};
...
customRequest = ({ onSuccess, onError, file }) => {
const checkInfo = () => {
setTimeout(() => {
if (!this.imageDataAsURL) {
checkInfo();
} else {
this.uploadFile(file)
.then(() => {
onSuccess(null, file);
})
.catch(() => {
// call onError();
});
}
}, 100);
};
checkInfo();
};
There are probably better ways to do it, but I hope that helps you.
回答2:
I struggled it a lot and find an efficient way to handle this case.
first- you should mess with the customRequest only when you need to change to body and the request type (like using post instead of 'put' or using xml or add another extra header).
for the signing Url you can send in the action prop callback which return a promise with the right Url to upload like:
handleUplaod = (file: any) => {
return new Promise(async (resolve, reject) => {
const fileName = `nameThatIwant.type`;
const url = await S3Fetcher.getPresignedUrl(fileName);
resolve(url);
});
and render like:
render(){
return(
....
<Upload
action={this.handleUplaod}
....
Upload>
the uploader take the url from the action prop.
the onChange method which is provided also will be called any time the status of upload is changed-
onChange# The function will be called when uploading is in progress, completed or failed.
When uploading state change, it returns:
{ file: { /* ... / }, fileList: [ / ... / ], event: { / ... */ }, }
when upload started you will need to activate the file reader from that. like:
....
fileReader = new FileReader();
.....
onChange = (info) => {
if (!this.fileReader.onloadend) {
this.fileReader.onloadend = (obj) => {
this.setState({
image: obj.srcElement.result, //will be used for knowing load is finished.
});
};
// can be any other read function ( any reading function from
// previously created instance can be used )
this.fileReader.readAsArrayBuffer(info.file.originFileObj);
}
};
notice when completed stage that event=undefind
To update the UI from the upload events you should use the options variables from customRequest and call them whenever you need.
onSuccess- should be called when you finish uploading and it will change the loading icon to the file name.
onError- will paint the file name filed to red.
onProgress- will update the progress bar and should be called with {percent: [NUMBER]} for updating.
for example in my code-
customRequest = async option => {
const { onSuccess, onError, file, action, onProgress } = option;
const url = action;
await new Promise(resolve => this.waitUntilImageLoaded(resolve)); //in the next section
const { image } = this.state; // from onChange function above
const type = 'image/png';
axios
.put(url, Image, {
onUploadProgress: e => {
onProgress({ percent: (e.loaded / e.total) * 100 });
},
headers: {
'Content-Type': type,
},
})
.then(respones => {
/*......*/
onSuccess(respones.body);
})
.catch(err => {
/*......*/
onError(err);
});
};
waitUntilImageLoaded = resolve => {
setTimeout(() => {
this.state.image
? resolve() // from onChange method
: this.waitUntilImageLoaded(resolve);
}, 10);
};
I used axios but you can use other libraries as well and the most important part-
render(){
return(
....
<Upload
onChange={this.onChange}
customRequest={this.customRequest}
...>
回答3:
onCustomRequest = file => {
return new Promise(((resolve, reject) => {
const ajaxResponseWasFine = true;
setTimeout(() => {
if (ajaxResponseWasFine) {
const reader = new FileReader();
reader.addEventListener('load', () => {
resolve(reader.result);
}, false);
if (file) {
reader.readAsDataURL(file);
}
} else {
reject('error');
}
}, 1000);
}));
};
<Dragger
action={this.onCustomRequest}
fileList={fileList}
onChange={this.handleChangeUpload}
className={styles.dragWrapper}
showUploadList={true}
beforeUpload={this.beforeUpload}
>
来源:https://stackoverflow.com/questions/47021594/how-should-customrequest-be-set-in-the-ant-design-upload-component-to-work-with