Uploading image with form data in React - html

I am trying to upload a photo in my React application, along with some form data. It works with uploading form data from ItemAdd.jsx, a child component of ItemList.jsx. However, when I try to also POST an image file with this data, the image property is undefined when it hits the server.
My suspicion is that I'm using the wrong content-type in the request, but I'm not sure what I should be using instead (if that is the issue here).
Parent Component - ItemList.jsx
import React from 'react';
import 'whatwg-fetch';
import classNames from 'classnames';
import ItemAdd from './ItemAdd.jsx';
export default class ItemList extends React.Component {
constructor() {
super();
this.createItem = this.createItem.bind(this);
}
createItem(newItem) {
console.log('PHOTO:', newItem.image);
fetch('/api/item', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newItem),
}).then(response => {
}).catch(err => {
});
}
render() {
return (
<div>
<ItemAdd createItem={this.createItem} />
</div>
);
}
}
Child Component - ItemAdd.jsx
import React from 'react';
export default class ItemAdd extends React.Component {
constructor() {
super();
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
image: null,
imagePreviewUrl: null
}
}
handleSubmit(e) {
e.preventDefault();
let form = document.forms.itemAdd;
this.props.createItem({
name: form.name.value,
image: this.state.image
});
// Clear the form and state for the next input.
form.name.value = "";
this.state.image = null;
this.state.imagePreviewUrl = null;
}
handleImageChange(e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
image: file,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file)
}
render() {
let { imagePreviewUrl } = this.state;
let $imagePreview = null;
if (imagePreviewUrl) {
$imagePreview = (<img src={imagePreviewUrl} className={'img-preview'} />);
} else {
$imagePreview = (<div className="previewText">Please select an image.</div>);
}
return (
<div>
<form name="itemAdd" onSubmit={this.handleSubmit}>
<table>
<tr>
<td><label for="name">Name:</label></td>
<td><input type="text" name="name" id="name" placeholder="Name" /></td>
</tr>
<tr>
<td><input type="file" onChange={(e) => this.handleImageChange(e)} /></td>
<td>
<div className="img-preview">
{$imagePreview}
</div>
</td>
</tr>
<tr>
<td><button>Add</button></td>
</tr>
</table>
</form>
</div>
);
}
}

You might not be able to post an image as part of JSON data, calling JSON.stringify() on an image is not a good idea.
I would recommend using formData to submit the form, which makes it multipart/form-data content type.
You might have to handle that differently in the backend.
Example :
createItem(newItem) {
console.log('PHOTO:', newItem.image);
const h = {}; //headers
let data = new FormData();
data.append('image', newItem.image);
data.append('name', newItem.name);
h.Accept = 'application/json'; //if you expect JSON response
fetch('/api/item', {
method: 'POST',
headers: h,
body: data
}).then(response => {
// TODO : Do something
}).catch(err => {
// TODO : Do something
});
}
You can read more on formData

Related

HttpErrorResponse {headers: HttpHeaders, status: 415, statusText: "Unsupported Media Type"

Hi I tried to upload a csv file and convert it to json array and pass to the web api. But when I click the submit button I am getting this error. Anyone who can help to fix this?
Thank you :)
This is my .ts file in angular Here I tried to upload a csv file and convert it to json array.
changeListener(event: any) {
if (event.target.files.length > 0) {
const file = event.target.files[0];
this.myForm.patchValue({
fileSource: file
});
//File reader method
let reader: FileReader = new FileReader();
reader.readAsText(file);
reader.onload = (e) => {
let csv: any = reader.result;
let allTextLines = [];
allTextLines = csv.split(/\r|\n|\r/);
console.log('CSV: ', csv?.toString());
}
//JSON.stringify(file);
}
}
submit() {
const formData = new FormData();
formData.append('file', this.myForm.get('fileSource')?.value);
this.http.post('http://localhost:64233/api/employee', formData)
.subscribe(res => {
console.log(res);
alert('Upload Sussessful');
})
}
This is my .html file in angular
<form [formGroup]="myForm" (ngSubmit)="submit()">
<h1 style="text-align: center">File Upload</h1>
<br /><br />
<div class="form-group">
<label for="file">File</label>
<input class="form-control" formControlName="file" id="file" type="file" class="upload"
(change)="changeListener($event)" />
</div>
<button id="btnSave" class="btn btn-primary" type="submit">Submit</button>
</form>
This is the error I get when I click on submit button
I checked my web api in postman and it is working fine for json array. Really appreciate if you can help. Thank you
Ok I am talking to myself. But this is to help others.
Here in my .ts file, I have uploaded the file and read the data in the csv file as a string. But I have not converted it to json array correctly and push it to go to the web api.
So below is the working code. This may not be the perfect one but it works fine for me.
This is my .ts file in angular
export class FileUploadComponent implements OnInit {
myForm = new FormGroup({
file: new FormControl('', [Validators.required])
});
ngOnInit(): void {
this.resetForm();
}
constructor(private http: HttpClient, public fileUploadService: FileUploadService,
private toastr: ToastrService, private router: Router) { }
obj: any;
unique: any;
removeHeader: any;
// Maximum file size allowed to be uploaded = 1MB
maxSize: number = 1048576;
//upload file
fileUpload(event: any) {
if (event.target.files && event.target.files.length > 0) {
// Don't allow file sizes over 1MB
if (event.target.files[0].size < this.maxSize) {
const file = event.target.files[0];
console.log(file);
//File reader method
let reader: FileReader = new FileReader();
reader.readAsText(file);
reader.onload = (e) => {
let csv: any = reader.result;
let res: any[] = csv.split("\n");
//remove first element of the array
res.shift();
let jsonArray: any = [];
res.forEach(item => {
let singlePerson = item.split(",");
let singleObject = { employeeid: singlePerson[0], firstname: singlePerson[1], lastname: singlePerson[2], address: singlePerson[3] }
jsonArray.push(singleObject);
})
this.obj = jsonArray;
//check duplicates in csv file, remove, and return unique records
let unique = this.obj
.map((e: { [x: string]: any; }) => e['employeeid'])
.map((e: any, i: any, final: string | any[]) => final.indexOf(e) === i && i)
.filter((obje: string | number) => this.obj[obje])
.map((e: string | number) => this.obj[e]);
this.obj = unique;
}
}
else {
// Display error message
this.toastr.error("File is too large to upload");
}
}
}
resetForm() {
this.myForm.reset();
}
submit() {
this.fileUploadService.postFileUpload(this.obj);
this.resetForm();
}
}
This is my html file
<br /><br />
<form [formGroup]="myForm">
<h1 style="text-align: center">File Upload</h1>
<br /><br />
<div class="form-group">
<label for="file" style="font-size: 25px">File</label>
<input
class="form-control"
formControlName="file"
type="file"
accept=".csv"
class="upload"
(change)="fileUpload($event)"
/>
</div>
<div class="form-group">
<label> Please Upload a CSV or Text file of size less than 1MB </label>
</div>
<button class="btn btn-primary" type="submit" (click)="submit()">
Submit
</button>
</form>
This is my service class
export class FileUploadService {
messages: string[] = [];
constructor(private http: HttpClient, private toastr: ToastrService) { }
readonly baseURL = 'http://localhost:64233/api/employee';
myForm = new FormGroup({
file: new FormControl('', [Validators.required])
});
formData: FileUpload = new FileUpload();
//method for post request
postFileUpload(body: any) {
const requestOptions = { headers: new HttpHeaders({ 'content-type': "application/json" }) };
return this.http.post(this.baseURL, body, requestOptions)
.subscribe(
observer => {
this.toastr.success("File Uploaded Succesfully");
this.resetForm();
},
err => {
if (err.status == 500)
this.toastr.error("Empty File");
else
this.toastr.error("Please upload a file");
//console.log(err);
/* (error) => {
console.log(error); */
//throw new Error(error);
});
}
resetForm() {
this.myForm.reset();
this.formData = new FileUpload();
}
}
Here to display alerts I have used a toaster

Object actions in vue

I have the following structure in Vue.
The App.vue
export default {
name : "app",
router,
data() {
return {
items: {books:[], authors:[]}
};
},
created: function() {
customServiceInstance.makeAjaxCall("books.json", "get").then(res => {
this.items.books = res.books;
return res;
})
customServiceInstance.makeAjaxCall("authors.json", "get").then(res => {
this.items.authors = res.authors;
return res;
})
customServiceInstance.makeAjaxCall("genres.json", "get").then(res => {
this.items.genres = res.genres;
return res;
})
},
methods: {
removeEntry:function(index) {
this.$delete(this.items.books, index);
customServiceInstance.makeAjaxCall('books.json', 'POST', JSON.stringify(this.items.books));
}
},
computed: {
booksWithAuthor () {
let { books, authors } = this.items
return books.map(book => ({
...book,
author: authors.find(author => author.id === book.author),
}))
},
}
}
</script>
<template>
<div id="app">
<router-link to="/home" >Home 1</router-link>
<router-link to="/home/2"> Home 2</router-link>
<router-view class="view" foo="123"></router-view>
<table class="booksTable">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Genre</th>
<th>Image</th>
<th>Availability</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<tr v-for="(book,index) in booksWithAuthor" v-bind:key="book.name">
<td>{{book.name}}</td>
<td>{{book.author.name}}</td>
<td>{{book.genre}}</td>
<td><img class="imageBook" :src="book.imageUrl"></td>
<td v-if="book.availability">Available</td>
<td v-else>Unavailable</td>
<td>
<button class="btn add">Add</button>
<button class="btn edit" >Edit</button>
<button class="btn delete" v-on:click="removeEntry(index)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import './styling.scss';
import customService from './components/customService';
const customServiceInstance= new customService();
import Vue from 'vue';
import VueRouter from 'vue-router';
import HomeR from './components/home.vue';
import Copil from './components/copil.vue';
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{path: '/home', component: HomeR},
{path: '/home/:years', component: Copil, props:true }
]
})
And this JS
export default class CustomService {
listJson(url){
var storageLocalData = localStorage.getItem(url);
var obj=JSON.parse(storageLocalData);
console.log(obj);
};
makeAjaxCall(url, methodType, data){
this.listJson(url);
var promiseObj = new Promise(function(resolve, reject){
var storageLocalData = localStorage.getItem(url);
if(!storageLocalData){
var xhr = new XMLHttpRequest();
xhr.open(methodType, url, true);
if (data) {
xhr.send(data);
} else {
xhr.send();
}
xhr.onreadystatechange = function(){
if (xhr.readyState === 4){
if (xhr.status === 200){
var response = xhr.responseText;
var respJson = JSON.parse(response);
localStorage.setItem(url, JSON.stringify(respJson));
resolve(respJson);
} else {
reject(xhr.status);
}
}
}
}
else {
resolve(JSON.parse(storageLocalData));
}
});
return promiseObj;
};
}
I want to create an object Book and have a function getBookById(id, list),
The list being the books.json that's being loaded.I want this function to return the book object, who has name, author, genre and so on.
I tried a lot of things, but with no result.
Even tried in a ts file something like this:
export default class Book {
name: String;
id: Number;
author: String;
genre: Number;
imageUrl: String;
availability: boolean;
methods: {
getBookById:(id: Number,url: String) => Book {
}
}
Please help me
I want to create an object Book and have a function getBookById(id,
list), The list being the books.json that's being loaded.I want this
function to return the book object, who has name, author, genre
this can be achieved by the es6 array function find().
all you have to do inside your function, is:
getBookById(bookId,booksList){
return booksList.find(book=>
book.id===bookId)
}
the function will return the first array item that matches the condition (book.id===bookId), or undefined if none of them did match.

Angular 6 file upload with form content

I have create a page with different input elements with file upload. While saving the form with multiple files along with form input elements using angular 6, the file object is empty {} in console an http service in network tab.
Here is my code:
onFileChanged(event) {
this.selectedFiles = event.target.files;
const uploadData = new FormData();
uploadData.append('myFile', this.selectedFiles, this.selectedFiles[0].name);
this.createFormData.attachment = uploadData;
};
Can anyone provide a sample to help me?
This is example of upload method in service. Pass files and input values from component to service, create formData, loop on files and append each file to formData and same with input values.
upload(files, inputs) {
let formData = new FormData();
files.map((file) => {
formData.append('file', file);
});
inputs.map((input) => {
formData.append(`${input.name}`, input.value);
});
return this.http.post(`some/api/upload`, formData)
.pipe(map((res) => res.data));
}
With this example your request should contain all array of files and inputs, also if you need some special header add it after formData in post method (in my case i handle headers in interceptor).
upload(files, inputs) {
let formData = new FormData();
files.map((file) => {
formData.append('file', file);
});
inputs.map((input) => {
formData.append(`${input.name}`, input.value);
});
const headers = new HttpHeaders({
'Accept': 'application/json'
});
const options = { headers: headers };
return this.http.post(`some/api/upload`, formData, options)
.pipe(map((res) => res.data));
}
Have a look at following example with npm's ng2-file-upload (in this case with a fontAwsome-icon).
myFileUploadForm.component.html:
<!-- file upload -->
<input #fileToUpload type="file" style="display:none;" ng2FileSelect (change)="onFileChanged($event)" [uploader]="uploader"
name="customerFile" id="customerFile" class="customerFile" />
<a href="javascript:document.getElementById('customerFile').click();">
<fa class="ql-upload" *ngIf="buttonDeaktivieren" [title]="'Upload'" [name]="'upload'" [size]="0.9" [border]=false></fa>
</a>
myFileUploadForm.component.ts (I'll ommit the obvious parts with ..):
import { Component, OnInit, ViewChild, OnDestroy, TemplateRef } from '#angular/core';
import { Subscription, Observable } from '../../../../../node_modules/rxjs';
....
...
import { FileUploader } from 'ng2-file-upload';
#Component({
....
...
..
})
export class myFileUploadFormComponent implements OnInit, OnDestroy {
public uploader: FileUploader = new FileUploader({ url:
'http://localhost:3000/files/uploadFile/', itemAlias: 'customerFile' });
filesArray = [];
constructor(
...
..
private http: Http
) { }
ngOnInit() {
....
...
..
}
// INFO: Function for opening confirm modal when deleting file
openDeleteFileModal(file) {
const initialState = {
file: file
};
this.deleteFileModalRef = this.modalService.show(ConfirmFileDeletionComponent, {
initialState, class: 'modal-md modal-dialog-centered' });
}
// INFO: Handy helper for assigning appropriate file-icon according to extension
getFilesIcon(file) {
if (file === 'docx') {
return 'file-word-o';
} else if (file === 'jpeg' || file === 'jpg' || file === 'png') {
return 'image';
} else if (file === 'pdf') {
return 'file-pdf-o';
} else if (file === 'xlsx' || file === 'xls') {
return 'file-excel-o';
} else if (file === 'pptx') {
return 'file-powerpoint-o';
} else if (file === 'zip') {
return 'file-archive-o';
} else {
return 'file';
}
}
/* INFO : service for downloading the uploaded file */
downloadFile(filename) {
this.fileDataService.downloadFile(filename);
}
onFileChanged(event) {
this.uploader.onBuildItemForm = (fileItem: any, form: any) => {
form.append('id', this.customer.id);
form.append('customername', this.customer.customername);
};
this.uploader.uploadAll();
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Obviously this is my implementation suited to my needs (uploaded files are related to a certain customer and are displayed with icons according to extension and listed for future download or deletion as well), but I'm sure you can get it done by adjusting the code to your needs and write the relevant services. Have a nice one weekend ;)

output data from json file in React

I need to output data from the json file that the user loads at the front.
I download the json file, but I get a strange store in Redux and can not figure out how to further display the data to the screen from the store.
What should I need to do?
import React from 'react'
import { connect } from 'react-redux'
import * as actions from './actions'
class File extends React.Component {
constructor (props) {
super(props)
}
handleChange (e) {
e.preventDefault();
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
let text = reader.result;
this.props.jsonLoad(JSON.parse(text))
}
reader.readAsText(file)
}
render () {
return (
<div>
<h3> Add your file: </h3>
<input type="file" multiple onChange={this.handleChange.bind(this)}/>
<button >
add text from file
</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
json: state.json
}
}
function mapDispatchToProps (dispatch) {
return {
jsonLoad: (json) => dispatch(actions.jsonLoad(json)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(File)
action.js
const USER_JSON = 'USER_JSON';
export function jsonLoad(json) {
console.log( json)
return function (dispatch) {
let file = json
dispatch(addFile(file))
}
}
export function addFile (json) {
return{
type: 'USER_JSON',
payload: json
}
};

json not render with reactjs and redux

i load a .json with axios, and the file load well, but when i rendered dont work
editprofile.js --> create the dispatch, and load de json
export const editProfile = (callback)=>{
return function(dispatch){
dispatch({type: 'EDIT_PROFILE_REQUEST'});
axios({
method: 'get',
url: 'https://gist.githubusercontent.com/anonymous/38c1444f753c70cf79ee980638a14de7/raw/34951eebfa006fea3db00fb492b491ac990c788e/vamos.json',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
.then((response)=>{
dispatch({type:'EDIT_PROFILE_SUCCESS', payload:response.data});
if (typeof callback === 'function') {
callback(null, response.data);
}
})
.catch((error) =>{
dispatch({type:'EDIT_PROFILE_FAILURE'});
if(error.response.status == 401){
browserHistory.push('login')
toastr.error(error.response.message, 'User')
}
if(typeof callback ==='function'){
callback(error.response.data, null)
}
})
}
}
EditProfileComponent.jsx -->created the component
export default class EditProfileComponent extends Component{
render(){
return(
<table>
<thead>
<tr>
<th>SN</th>
<th>Email</th>
<th>created</th>
</tr>
</thead>
<tbody>
{this.renderEditProfile()}
</tbody>
</table>
)
}
renderEditProfile(){
let sN = 1;
return this.props.allProfile.map((user)=>{
return(
<tr key={user.sN} >
<td>{sN++}</td>
<td>{user.email ? user.email : '---'}</td>
<td>{user.created_at ? user.created_at : '---'}</td>
</tr>
);
});
}
}
join the component with the service
import {editProfile} from '../action/editProfile.js';
import EditProfileComponent from '../component/editProfileComponent.jsx';
export default class EditProfileContainer extends Component{
componentDidMount(){
this.props.editProfile();
}
render (){
return (
<EditProfileComponent allProfile={this.props.allProfile} />
);
}
}
function mapStateToProps(store) {
return {
allProfile:store.allProfile
};
}
function matchDispatchToProps(dispatch){
return bindActionCreators({
editProfile:editProfile
}, dispatch)
}
export default connect
(mapStateToProps, matchDispatchToProps)(EditProfileContainer);
editProfileReducer --> the reducer
export const editProfileReducer = (state=[], action) =>{
switch(action.type){
case 'EDIT_PROFILE_REQUEST':
return state;
case 'EDIT_PROFILE_FAILURE':
return state;
case 'EDIT_PROFILE_SUCCESS':
return [...action.payload];
default:
return state;
}
}
join all the reducer
import { editProfileReducer } from './reducer/editProfileReducer.js'
const reducers = combineReducers({
allProfile:editProfileReducer,
});
export default reducers;
There is an error in your reducer. For EDIT_PROFILE_SUCCESS, it should be
case 'EDIT_PROFILE_SUCCESS':
return [...state, action.payload];
On a side note, you can take advantage of es6's arrow function:
export const editProfile = (callback) => (dispatch) => {
dispatch({type: 'EDIT_PROFILE_REQUEST'});
// ....
};
You also should use constants for action names.
I think there is problem with :
function mapStateToProps(store) {
return {
allProfile:store.allProfile
};
}
it should be:
function mapStateToProps(state) {
return {
allProfile:state.allProfile
};
}