I want to show a notification with the upload status. I took over a project in React & ASP.NET and I am relatively new to this. The question is quite simple, yet I am struggling to solve it: How do I display a popup notification showing which files have been successfully been uploaded and which not?
import * as React from "react";
import { connect } from "react-redux";
import { Form, Select, Button, Upload, message, notification} from 'antd';
import * as Actions from "../actions";
const FormItem = Form.Item;
class UploadFileForm extends React.Component<any, any> {
constructor(props: any) {
super(props);
}
handleSubmit = (e) => {
message.config({ top: 0 });
message.loading('Importing in progress...', 3);
e.preventDefault();
this.props.uploadFile(this.props.form.getFieldsValue());
notification["info"]({
message: 'Files successfully uploaded',
description: '', // <-- this line has to be modified
duration: 10
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit}>
<FormItem label="File" >
{getFieldDecorator('upload', {
valuePropName: 'fileList',
getValueFromEvent: (e) => e.fileList.slice(-1)
})(
<Upload name="importFile" action={' '} multiple={false}>
<Button> Upload </Button>
</Upload>
)}
</FormItem>
<Button type="primary" htmlType="submit">Import</Button>
</Form>
);
}
}
export default Form.create()(UploadFileForm);
More specifically: How do I have to modify the line description: '', to show me a list of all uploaded files and their status as pure text, e.g. File(s) '1.txt', '2.txt', and '3.txt' have been successfully uploaded. File(s) '4.txt' failed.?
The project documentation says that we are using Redux-Saga, but I am not so maybe that makes the story easier.
I guess your this.props.uploadFile method is a promise so considering that you should show notification once that promise is resolved
this.props.uploadFile(this.props.form.getFieldsValue()).then(result => {
// since your client doesn't know which ones are success/failed, server should return
// this information when request is finished
const { successUploads, failUploads } = result;
notification["info"]({
message: 'Files successfully uploaded',
description: `File(s) ${successUploads.join(', ')} have been successfully uploaded. File(s) ${failUploads.join(', ')} failed.`
duration: 10
});
});
If you can't control whats returned from the server then you'd need to track uploads on client side, but that would mean having multiple uploads (requests) to the server and your upload method would look something like this:
async function uploadFiles(files) {
// I've called your server upload uploadService.send(), but replace this with your method
const results = await Promise.all(
files.map(file => uploadService.send(file))
.map(p => p.catch(e => e)
);
let successUploads = [];
let failUploads = [];
results.forEach((result, idx) => {
const file = files[idx];
if (result instanceof Error) {
failUploads.push(file);
} else {
successUploads.push(file);
}
});
return {
successUploads,
failUploads
}
}
Then you could call uploadFiles same way as shown in first snippet.
Related
I am trying to post comments using axios. When I submit my datas entered in the form, I see this error in the console :
AxiosError {message: 'Request failed with status code 400', name: 'AxiosError', code: 'ERR_BAD_REQUEST', config: {…}, request: XMLHttpRequest, …}
Here is my code :
import React, { useState } from 'react'
import TextField from '#material-ui/core/TextField';
import { Button } from '#material-ui/core'
import CommentsAPI from '../../Services/CommentsAPI'
export default function CommentForm() {
const [comment, setComment] = useState({})
const handleSubmit = async (event) => {
event.preventDefault();
try {
const data = CommentsAPI.create(JSON.stringify(comment))
console.log(data)
} catch (error) {
console.log(error)
}
}
const handleChange = (event) => {
const {name, value} = event.currentTarget
setComment({
...comment,
[name]: value
})
}
return (
<form onSubmit={handleSubmit}>
<div>
<TextField
id="pseudo"
label="Pseudo"
type="text"
onChange={handleChange}
name="pseudo"
/>
</div>
<div>
<TextField
id="outlined-multiline-static"
label="Comment"
multiline
minRows={2}
onChange={handleChange}
name="content"
/>
</div>
<div>
<Button variant="contained" color="primary" type="submit">
Send
</Button>
</div>
</form>
)
}
CommentsAPI.js file :
import { URL_COMMENTS } from '../config'
import axios from 'axios'
function create(comment) {
return axios.post(URL_COMMENTS, comment)
}
const CommentsAPI = {
create
}
export default CommentsAPI
I am trying to understand what is wrong. Thank you very much for your help !
Have a look on my server :
Collection type
Permission with POST api url
You're not sending anything to your API. CommentsAPI.create(YOUR COMMENT HERE)
const handleSubmit = async (event) => {
event.preventDefault();
try {
// const data = CommentsAPI.create() // WRONG !
// Create expects a comment, send something !
const data = CommentsAPI.create('This is a test');
// Or send the valu in your state
// const data = CommentsAPI.create(comment.content);
} catch (error) {
console.log(error)
}
}
Also, in your server you will need to return helpful error message. Like 'Hey, there is no message, please send a message in the payload'. That will help you understand better what's going on.
For anyone else, who is facing the same issue, try changing your get http request to post, if you are sending data from body that has a list.
Hope this helps.
If you receive a 400 (https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors), it means that the sever received your request but the content was not valid. Read the documentation of the API to be sure you send the correct payload.
By default, if Axios receives something other than a 2xx, it will throw an exception
And if you want your
console.log(data)
to work, do not forget to add await:
await console.log(data)
so that the code awaits the answer of the server before trying to console.log() it
Your problem is here...
JSON.stringify(comment)
This passes a string to Axios which it will interpret as text/plain and set the request content-type header to the same.
It's highly likely your API expects an application/json or application/x-www-form-urlencoded request body and rejects a plain text one.
To send the former, simply omit JSON.stringify() and let Axios deal with serialisation and content-type detection
// don't forget to `await`
const { data } = await CommentsAPI.create(comment);
The latter can be achieved using URLSearchParams
const { data } = await CommentsAPI.create(new URLSearchParams(comment));
I am looking into fixing a bug in the code. There is a form with many form fields. Project Name is one of them. There is a button next to it.So when a user clicks on the button (plus icon), a popup window shows up, user enters Project Name and Description and hits submit button to save the project.
The form has Submit, Reset and Cancel button (not shown in the code for breviety purpose).
The project name field of the form has auto suggest feature. The code snippet below shows the part of the form for Project Name field.So when a user starts typing, it shows the list of projects
and user can select from the list.
<div id="formDiv">
<Growl ref={growl}/>
<Form className="form-column-3">
<div className="form-field project-name-field">
<label className="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-animated custom-label">Project Name</label>
<AutoProjects
fieldName='projectId'
value={values.projectId}
onChange={setFieldValue}
error={errors.projects}
touched={touched.projects}
/>{touched.projects && errors.v && <Message severity="error" text={errors.projects}/>}
<Button className="add-project-btn" title="Add Project" variant="contained" color="primary"
type="button" onClick={props.addProject}><i className="pi pi-plus" /></Button>
</div>
The problem I am facing is when some one creates a new project. Basically, the autosuggest list is not showing the newly added project immediately after adding/creating a new project. In order to see the newly added project
in the auto suggest list, after creating a new project,user would have to hit cancel button of the form and then open the same form again. In this way, they can see the list when they type ahead to search for the project they recently
created.
How should I make sure that the list gets immediately updated as soon as they have added the project?
Below is how my AutoProjects component looks like that has been used above:
import React, { Component } from 'react';
import Autosuggest from 'react-autosuggest';
import axios from "axios";
import { css } from "#emotion/core";
import ClockLoader from 'react-spinners/ClockLoader'
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// Use your imagination to render suggestions.
const renderSuggestion = suggestion => (
<div>
{suggestion.name}, {suggestion.firstName}
</div>
);
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
export class AutoProjects extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
projects: [],
suggestions: [],
loading: false
}
this.getSuggestionValue = this.getSuggestionValue.bind(this)
this.setAutoSuggestValue = this.setAutoSuggestValue.bind(this)
}
// Teach Autosuggest how to calculate suggestions for any given input value.
getSuggestions = value => {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp(escapedValue, 'i');
const projectData = this.state.projects;
if (projectData) {
return projectData.filter(per => regex.test(per.name));
}
else {
return [];
}
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion. Teach Autosuggest how to calculate the
// input value for every given suggestion.
getSuggestionValue = suggestion => {
this.props.onChange(this.props.fieldName, suggestion.id)//Update the parent with the new institutionId
return suggestion.name;
}
fetchRecords() {
const loggedInUser = JSON.parse(sessionStorage.getItem("loggedInUser"));
return axios
.get("api/projects/search/getProjectSetByUserId?value="+loggedInUser.userId)//Get all personnel
.then(response => {
return response.data._embedded.projects
}).catch(err => console.log(err));
}
setAutoSuggestValue(response) {
let projects = response.filter(per => this.props.value === per.id)[0]
let projectName = '';
if (projects) {
projectName = projects.name
}
this.setState({ value: projectName})
}
componentDidMount() {
this.setState({ loading: true}, () => {
this.fetchRecords().then((response) => {
this.setState({ projects: response, loading: false }, () => this.setAutoSuggestValue(response))
}).catch(error => error)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
// Autosuggest will call this function every time you need to update suggestions.
// You already implemented this logic above, so just use it.
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: this.getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render() {
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: value,
value,
onChange: this.onChange
};
// Finally, render it!
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<div className="sweet-loading">
<ClockLoader
css={override}
size={50}
color={"#123abc"}
loading={this.state.loading}
/>
</div>
</div>
);
}
}
The problem is you only call the fetchRecord when component AutoProjects did mount. That's why whenever you added a new project, the list didn't update. It's only updated when you close the form and open it again ( AutoProjects component mount again)
For this case I think you should lift the logic of fetchProjects to parent component and past the value to AutoProjects. Whenever you add new project you need to call the api again to get a new list.
Background
When running my app over localhost, I can choose my PDF file and submit it. I'm able to get the path of the IPFS file and display the path in the console.
Problem
When adding this line to display my file, it doesn't work and shows "No PDF file specified" instead.
<Document src={https://ipfs.infura.io/ipfs/${this.state.ipfshash}} />
<Document file={https://ipfs.infura.io/ipfs/${this.state.docupayHash}} />
What I've Tried
I've gone to the link in Google Chrome (ipfs.infura.io/ipfs/"QmUqB9dWDCeZ5nth9YKRJTQ6PcnfrGPPx1vzdyNWV6rh8s") and I can see the file there, so I know the link is correct.
Code
App.js
import React, { Component } from "react";
import { Document, Page } from 'react-pdf';
import web3 from "./web3";
import ipfs from "./ipfs";
import storehash from "./storehash";
import "./styles/App.css";
class App extends Component {
state = {
contractHash: null,
buffer: "",
ethAddress: "",
blockNumber: "",
transactionHash: ""
};
captureFile = (event) => {
event.stopPropagation()
event.preventDefault();
const file = event.target.files[0];
let reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = () => this.convertToBuffer(reader);
};
convertToBuffer = async (reader) => {
// Convert file to buffer so that it can be uploaded to IPFS
const buffer = await Buffer.from(reader.result);
this.setState({buffer});
};
onClick = async () => {
try {
await web3.eth.getTransactionReceipt(this.state.transactionHash, (err, txReceipt) => {
console.log(err, txReceipt);
this.setState({txReceipt});
});
} catch (error) {
console.log(error);
}
}
onSubmit = async (event) => {
event.preventDefault();
// Take the user's MetaMask address
const accounts = await web3.eth.getAccounts();
console.log("Sending from Metamask account: " + accounts[0]);
// Retrieve the contract address from storehash.js
const ethAddress= await storehash.options.address;
this.setState({ethAddress});
// Save document to IPFS, return its hash, and set it to state
await ipfs.add(this.state.buffer, (err, contractHash) => {
console.log(err, contractHash);
this.setState({ contractHash: contractHash[0].hash });
storehash.methods.setHash(this.state.contractHash).send({ from: accounts[0] }, (error, transactionHash) => {
console.log(transactionHash);
this.setState({transactionHash});
});
})
};
render() {
return (
<div className="app">
<h3> Choose file to send to IPFS </h3>
<form onSubmit={this.onSubmit}>
<input type="file" onChange={this.captureFile} />
<button type="submit">Submit</button>
</form>
<Document file={`https://ipfs.infura.io/ipfs/${this.state.contractHash}`} />
<a href={`https://ipfs.infura.io/ipfs/${this.state.contractHash}`}>Click to download the file</a>
<button onClick = {this.onClick}>Get Transaction Receipt</button>
<p>IPFS Hash: {this.state.contractHash}</p>
<p>Contract Address: {this.state.ethAddress}</p>
<p>Tx Hash: {this.state.transactionHash}</p>
</div>
);
}
}
export default App;
MyContract.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.16 <0.7.0;
contract MyContract {
string contractHash;
function setHash(string memory ipfsHash) public {
contractHash = ipfsHash;
}
function getHash() public view returns (string memory ipfsHash) {
return contractHash;
}
}
I've looked at other solutions on SO but none that I found were particularly related to my question. Thank you for your help and time!
Two things to try:
Add ?filename= parameter as a hint for both gateway and react-pdf:
<Document src={`https://ipfs.infura.io/ipfs/${this.state.ipfshash}?filename=test.pdf`} />
This will make content-type returned by the gateways more reliable and eliminate false-negatives in react-pdf.
Run your own gateway, or contact Infura and discuss raising request limits for your app.
FYI I've run below test multiple times:
$ curl -Ls 'https://dweb.link/ipfs/QmUqB9dWDCeZ5nth9YKRJTQ6PcnfrGPPx1vzdyNWV6rh8s?filename=test.pdf' > output && file output
output: PDF document, version 1.5
After a few times they stop returning PDF, instead they return HTML page with 429 Too Many Requests error:
output: HTML document, ASCII text, with CRLF line terminators
$ cat output
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>openresty</center>
</body>
</html>
It is very likely that react-pdf is unable to render your PDF because it gets 429 Too Many Requests error response instead of the PDF payload.
Hello I am working on a Meteor-React project and fairly new in this. I am trying to insert new object into existing collection but it shows error as indicated in the title.
This is my component responsible for inserting new item at the UI level.
import React, { useState, useEffect } from "react";
import newTask from '../api/create'
export default Create = () => {
const [task, createTask] = useState('');
handleKeyPress = (event) => {
if(event.key === 'Enter'){
newTask(task)
}
}
return (
<form className="add-task" noValidate="">
<div>
<div className="fieldset add-task-input fieldset-stripped">
<div className="fieldset-content">
<label className="fieldset-label">
<span className="fieldset-label-content has-icon">
<i className="icon-plus" />
</span>
<input
className=""
name="title"
placeholder="Add new task"
type="text"
autoComplete="off"
value={task}
onChange={(e) => { createTask(e.target.value)}}
onKeyPress={this.handleKeyPress}
/>
</label>
</div>
</div>
</div>
</form>
)
}
This is the method that I am trying to export and import into the component file above.
import { Tasks } from '../../tasks';
export default newTask = (taskTitle) => Tasks.insert({
title: taskTitle,
dueDate: null,
repeat: {},
color: '#4e42c3',
status: 'incomplete',
customFields: []
})
I have tried using methods proposed by others as below, by adding the code below into the method file above:
Tasks.allow({
insert: function () {
return true;
}
})
But still it does not work and show the same error. Any idea how to enable inserting new item into the Mongo Collection ?
The allow/deny is opening a security vulnerability. You may rather use it only for prototyping / mocking some initial software prototypes.
The correct way to update collections is to use Meteor methods:
server
import { Tasks } from '../path/to/tasks'
Meteor.methods({
'insertTask': function ({ title, dueDate, repeat, color, status, customFields }) {
return Tasks.insert({ title, dueDate, repeat, color, status, customFields })
}
})
client
import { Tasks } from '../../tasks';
export default newTask = (taskTitle) => Meteor.call('insertTask', {
title: taskTitle,
dueDate: null,
repeat: {},
color: '#4e42c3',
status: 'incomplete',
customFields: []
})
Edit: If you only want to insert the document at the UI level and never have a collection on the server stored, you may make the Task collection local by exporting it wihtout a name:
client
export const Tasks = new Mongo.Collection(null)
Are you using autopublish package?
Your trying to insert the object from the client side. Without autopublish package you can't do it. Either add autopublish or insert from the server side.
So previously I added the code below
Tasks.allow({
insert: function () {
return true;
}
})
at the client side. Once I moved it to server side, it solves my problem.
I'm using Cypress.io to automate one file upload test case based on a react page. The input component(type=file) for file upload is created during runtime when the page is rendered.
Seems the button (by clicking the 'Choose file') opens a native file picker, which cypress Webdriver doesn't seem to support interacting with, so probably trigger an event to simulate file selection can be an option in this case. But the input(type=file) can't be located by Cypress because it is not a part of DOM, which means cy.get('input[type=file]') returns null.
Could you please give me some thoughts how to do it?
this button opens a native file picker
I've tried with this -
const testfile = new File(['test data to upload'], 'upload.csv')
cy.get('input[type=file]').trigger('change', {
force: true,
data: testfile,
});
this brings no luck,because of
CypressError: Timed out retrying: Expected to find element: 'input[type=file]', but never found it.
The source code of the page:
import React, { Component } from 'react'
interface Props {
text?: string
type?: string | undefined
fileID?: string
onFileSelected: (file: any) => void
}
interface State {
name: string
}
export default class FileUpload extends Component<Props, State> {
fileSelector = document.createElement('input')
state: State = {
name: '',
}
componentDidMount() {
this.fileSelector = this.buildFileSelector()
}
buildFileSelector = () => {
const { fileID, type } = this.props
this.fileSelector.setAttribute('type', 'file')
this.fileSelector.setAttribute('id', fileID || 'file')
this.fileSelector.setAttribute('multiple', 'multiple')
this.setAcceptType(type)
this.fileSelector.onchange = this.handleFileChange
return this.fileSelector
}
setAcceptType = (type: string | undefined) => {
if (type) {
type = type[0] === '.' ? type : type.replace(/^/, '.')
this.fileSelector.setAttribute('accept', type)
}
}
handleFileChange = (event: any) => {
const file = event.target.files[0]
if (file) {
this.setState({ name: file.name })
this.props.onFileSelected(file)
}
}
render() {
const { name } = this.state
return (
<div>
<button
onClick={(event: React.ChangeEvent<any>) => {
event.preventDefault()
this.fileSelector.click()
}}
style={{ marginRight: 10 }}
>
{this.props.text || 'Choose file'}
</button>
<label>{name || 'No file chosen'}</label>
</div>
)
}
}
I look forward to receiving suggestions how to automate this 'choose file' action in Cypress. Thanks in advance.
I sorted out this issue by putting an input(type=file) element into the DOM, so that cypress can locate the element and manipulate it.
But regarding the issue I had before I still would like to hear some insights from you if this is still possible to be handled in cypress.