I am building an image upload form using Next.js/React.js, and I want the user to be able to assign a tag to each upload. I also want to show the image preview using 'URL.createObjectURL'. The uploading works fine, but on the upload page where I try to iterate through the list of images to show the preview and show an input box to assign the tag, none of this is showing. I cannot work out why.
The code:
import { useState } from "react";
import Head from 'next/head'
import Layout, { siteTitle } from '../../components/layout'
import Image from 'next/image'
export default function PrivatePage(props) {
const [images, setImages] = useState([])
const [imageURLS, setImageURLS] = useState([])
const [tag, setTag] = useState(null)
const uploadImageToClient = (event) => {
var imageList = images
var urlList = imageURLS
if (event.target.files && event.target.files[0]) {
imageList.push(event.target.files[0]);
urlList.push(URL.createObjectURL(event.target.files[0]))
setImages(imageList);
setImageURLS(urlList);
}
};
const uploadTagToClient = (event) => {
if (event.target.value) {
const i = event.target.value;
setTag(i);
}
};
const uploadToServer = async (event) => {
const body = new FormData()
images.map((file, index) => {
body.append(`file${index}`, file);
});
body.append("tag", tag)
const response = await fetch("/api/file", {
method: "POST",
body
});
};
return (
<Layout home>
<Head>
<title>{siteTitle}</title>
</Head>
<div className="container">
<div className="row">
<h4>Select Images</h4>
<div className="col">
<input type="file" className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
<input type="file" className="btn btn-outline-success-inverse" onChange={uploadImageToClient} />
</div>
<div className="col">
</div>
<button
className="btn btn-outline-success-inverse"
type="submit"
onClick={uploadToServer}
>
Send to server
</button>
{images.map((file, index) => {
return (
<div class="row ">
<lead>{file.name}</lead>
<input type="text" onChange={uploadTagToClient} />
<image src={imageURLS[index]}/>
</div>)
})}
</div>
</div>
</Layout>
);
}
To clarify, nothing inside images.map is showing when I select images.
The issue happens because you're mutating the arrays you have in state with imageList.push and urlList.push which React doesn't pick up. This means state doesn't actually get updated and a re-render doesn't occur.
To fix it, rather than mutating those state arrays you need to create new ones when updating them.
const uploadImageToClient = (event) => {
if (event.target.files && event.target.files[0]) {
setImages((imageList) => [...imageList, event.target.files[0]]);
setImageURLS((urlList) => [
...urlList,
URL.createObjectURL(event.target.files[0])
]);
}
};
Unrelated to the main issue, you have several minor issues inside the render part of your component, specifically inside images.map.
You need to set a key prop on the outer <div> element;
The <lead> element doesn't exist, and needs to be replaced with a valid element;
The <image> element also doesn't exist, you probably meant <img> or <Image> (from next/image).
You can handle image upload with multiple image preview with following code.
const handleFile = (e) => {
setMessage("");
let file = e.target.files;
for (let i = 0; i < file.length; i++) {
const fileType = file[i]['type'];
const validImageTypes = ['image/gif', 'image/jpeg', 'image/png'];
if (validImageTypes.includes(fileType)) {
setFile([...files,file[i]]);
} else {
setMessage("only images accepted");
}
}
};
Follow this snippet https://bbbootstrap.com/snippets/multiple-image-upload-preview-and-remove-92816546
Related
import { useState } from "react";
function Image({ image }) {
const [favourite, setFavourite] = useState([]);
const storeFavourites = () => {
setFavourite((gif) => [...gif, image]);
console.log(favourite);
};
const viewFavourites = () => {
favourite.map((url) => {
<img src={url} />;
});
};
return (
<div>
<img src={image} />
<button onClick={storeFavourites}>Like</button>
<button onClick={viewFavourites}>View Favourites</button>
</div>
);
}
export default Image;
This is a giphy site which generates a random gif when loaded and will generate a different gif based on search results. There is a like button to like the image and it was able to store the image as favourites in a state under const [favourite, setFavourite], however I was unable to display any of the favourite images when I clicked on View Favourites.
Looks like you are returning the view from the function which is not used anywhere inside the component's return function, which is the reason why nothing is rendered.
Here's something what you could do:
Store the favourite images in a list.
Display the image when a state value is toggled.
So, Here's what you could change:
import { useState } from "react";
function Image({ image }) {
const [favourite, setFavourite] = useState([]);
const [showFavourites, setShowFavourites] = useState(false);
const storeFavourites = () => {
setFavourite((gif) => [...gif, image]);
console.log(favourite);
};
const viewFavourites = () => {
setShowFavourites(!showFavourites);
};
return (
<div>
<img src={image} />
<button onClick={storeFavourites}>Like</button>
<button onClick={viewFavourites}>View Favourites</button>
{showFavourites && favourite.map((url, index) => {
<img src={url} key={index}
})}
</div>
);
}
export default Image;
If you don't wish to toggle the view but instead just it to change just one time, you could change the viewFavourites to this:
const viewFavourites = () => {
setShowFavourites(true);
};
In essence I am trying to do as the link below:
https://docs.strapi.io/developer-docs/latest/plugins/upload.html#upload-files-related-to-an-entry
My code is just slightly different but should achieve the same goal of adding the new file as a field to a entry of a Content-Type:
import React from 'react'
import { useState } from 'react'
import { API_URL } from '#/config/index'
import styles from '#/styles/Form.module.css'
export default function ImageUpload({ evtId, imageUploaded }) {
const [image, setImage] = useState(null)
const handleSubmit = async (e) => {
console.log('handleSubmit')
e.preventDefault()
const formData = new FormData() // pure javascript nothing to do with react
formData.append('files', image)
// formData.append('ref', 'events') //'ref' The collection we want to use
formData.append('ref', 'api::event.event')
formData.append('refId', evtId) //'refId' The event Id
formData.append('field', 'image') //'field' the image field we called 'image'
const res = await fetch(`${API_URL}/api/upload`, {
method: 'POST',
body: formData,
})
if (res.ok) {
console.log('res.ok')
console.log('res', res)
imageUploaded()
}
}
const handleFileChange = (e) => {
console.log('handleFileChange')
console.log(e.target.files[0]) //this will give us an array and we want the first wone so we add 0
setImage(e.target.files[0])
}
return (
<div className={styles.form}>
<h1> Upload Event Image</h1>
<form onSubmit={handleSubmit}>
<div className={styles.file}>
<input type='file' onChange={handleFileChange} />
</div>
<input type='submit' value='Upload' className='btn' />
</form>
</div>
)
}
The code above works perfectly when I use it for the first time and upload the first image I want to add as a value to the field in a event entry in the event Collection-Type.
However, if I decide that I no longer want that initial image as the value and would like to update it, if I use the same method above it doesn't work.
In the tutorial I'm following that is using perhaps v3 of Strapi, they were able to update/replace the image file just by using the same code.
How do I do the same thing for v4?
I'm not sure if this is the best and highest answer however it works for at the end of the day. The tutorial I'm following was perhaps working with v3 and it didn't have to delete the image to update it.
In my case for v4 I couldn't find a exact corresponding code for it. What I decided to do was to delete the file before uploading the new replacement file which gives me the same result at the end.
import React from 'react'
import { useState } from 'react'
import { API_URL } from '#/config/index'
import styles from '#/styles/Form.module.css'
export default function ImageUpload({ evtId, imageUploaded, imgId }) {
const [image, setImage] = useState(null)
const handleSubmit = async (e) => {
console.log('handleSubmit')
e.preventDefault()
const formData = new FormData() // pure javascript nothing to do with react
formData.append('files', image)
// formData.append('ref', 'events') //'ref' The collection we want to use
formData.append('ref', 'api::event.event')
formData.append('refId', evtId) //'refId' The event Id
formData.append('field', 'image') //'field' the image field we called 'image'
var uploadFormData = async () => {
const res = await fetch(`${API_URL}/api/upload`, {
method: 'POST',
body: formData,
})
if (res.ok) {
console.log('res.ok')
console.log('res', res)
imageUploaded()
}
}
if (imgId === null) {
console.log('imgId is null')
uploadFormData()
} else {
console.log('imgId not null')
const resDelete = await fetch(
`${API_URL}/api/upload/files/${imgId}`,
{
method: 'DELETE',
// body: formData,
}
)
if (resDelete.ok) {
console.log('resDelete.ok')
console.log('resDelete', resDelete)
uploadFormData()
}
}
}
const handleFileChange = (e) => {
console.log('handleFileChange')
console.log(e.target.files[0]) //this will give us an array and we want the first wone so we add 0
setImage(e.target.files[0])
}
return (
<div className={styles.form}>
<h1> Upload Event Image</h1>
<form onSubmit={handleSubmit}>
<div className={styles.file}>
<input type='file' onChange={handleFileChange} />
</div>
<input type='submit' value='Upload' className='btn' />
</form>
</div>
)
}
I have rendered a PDF file in react. I want to add a signature image to it by choosing an image file from the system and place it wherever I place the cursor on a pdf file.
After adding the signature we have to download the pdf along with the signature image on it.
I have used the #react-pdf-viewer library.
I tried but I don't know how to exactly approach this problem.
import React, { useState } from "react";
// Import the main component
import { Viewer } from "#react-pdf-viewer/core"; // install this library
// Plugins
import { defaultLayoutPlugin } from "#react-pdf-viewer/default-layout"; // install this library
// Import the styles
import "#react-pdf-viewer/core/lib/styles/index.css";
import "#react-pdf-viewer/default-layout/lib/styles/index.css";
// Worker
import { Worker } from "#react-pdf-viewer/core"; // install this library
export const App = () => {
// Create new plugin instance
const defaultLayoutPluginInstance = defaultLayoutPlugin();
//for unchange event
const [pdfFile, setPdfFile] = useState(null);
const [pdfFileError, setPdfFileError] = useState("");
//for submit event
const [viewPdf, setViewPdf] = useState(null);
// onchange event
const fileType = ["application/pdf"];
const handlePdfFileChange = (e) => {
let selectedFile = e.target.files[0];
if (selectedFile) {
if (selectedFile && fileType.includes(selectedFile.type)) {
let reader = new FileReader();
reader.readAsDataURL(selectedFile);
reader.onloadend = (e) => {
setPdfFile(e.target.result);
setPdfFileError("");
};
} else {
setPdfFile(null);
setPdfFileError("Please select valid pdf file");
}
} else {
console.log("select your file");
}
};
// form submit
const handlePdfFileSubmit = (e) => {
e.preventDefault();
if (pdfFile !== null) {
setViewPdf(pdfFile);
} else {
setViewPdf(null);
}
};
return (
<div className="container">
<br></br>
<form className="form-group" onSubmit={handlePdfFileSubmit}>
<input
type="file"
className="form-control"
required
onChange={handlePdfFileChange}
/>
{pdfFileError && <div className="error-msg">{pdfFileError}</div>}
<br></br>
<button type="submit" classname="btn btn-success btn-lg">
UPLOAD
</button>
</form>
<br></br>
<h4>View PDF</h4>
<div className="pdf-container">
{/* show pdf */}
{/* show pdf conditionally (if we have one) */}
{viewPdf && (
<>
<Worker workerUrl="https://unpkg.com/pdfjs-dist#2.6.347/build/pdf.worker.min.js">
<Viewer
fileUrl={viewPdf}
plugins={[defaultLayoutPluginInstance]}
/>
</Worker>
</>
)}
{/* if we dont have pdf or viewPdf state is null */}
{!viewPdf && <>No pdf file selected</>}
</div>
</div>
);
};
export default App;
My code for rendering PDF
Basically, in my use case, I'm receiving File Object from Parent to the Download component through the props and I want to be able to straight away download it. Is there a way I can do that ?
Please find below reference. [I know, it looks a bit weird in Parent component to select file using file input and to be able to download it just then. This is just for the sake of simplicity.]
Download Button
const DocumentMessage = ({file, type, label, ...props}) => {
return (
<Button as='a' style={{textDecoration: 'none'}} target='_blank' href={file} {...props}>{label}</Button>
);
}
Example of Parent Component where it's used -
export const documentMessageDefault = () => {
const [selectedFile, setSelectedFile] = useState(null);
const handleFileChange = (e) => {
file = e.target.files[0];
setSelectedFile(file);
}
return (
<div>
<input type='file' onChange={handleFileChange} />
<DocumentMessage file={selectedFile} label= 'Download File' />
</div>
);
}
A component of a library I'm using is essentially a wrapper around <input type=file>, which manages the buttons to upload a new image, change it or remove it, while also providing a thumbnail of it. This is its simplified definition:
import defaultImage from "assets/images/default_image.jpg";
export default function ImageUpload(props) {
const { image, setImage } = props;
const [imagePreviewUrl, setImagePreviewUrl] = useState(defaultImage);
let fileInput = React.createRef();
const handleImageChange = e => {
e.preventDefault();
let reader = new FileReader();
const imageFile = e.target.files[0];
reader.onloadend = () => {
const image = reader.result;
setImage(image);
setImagePreviewUrl(image);
};
reader.readAsDataURL(imageFile);
};
const handleClick = () => {
fileInput.current.click();
};
const handleRemove = () => {
setImage("");
setImagePreviewUrl(defaultImage);
fileInput.current.value = null;
};
return (
<div>
<input
type="file"
onChange={handleImageChange}
ref={fileInput}
/>
<div>
<img src={imagePreviewUrl} />
</div>
<div>
{image === null ? (
<Button onClick={() => handleClick()}> {"Select image"} </Button>
) : (
<span>
<Button {...changeButtonProps} onClick={() => handleClick()}> Change </Button>
<Button {...removeButtonProps} onClick={() => handleRemove()}> Remove </Button>
</span>
)}
</div>
</div>
);
}
The parent is basically collecting this and other components's states and stores them into a redux store. All works well, until I try to clear the preview image.
As input is not a controlled component in react, I'm not quite sure how to do that.
I though about passing the setImagePreviewUrl as a prop, but I'm not entirely sure how to implement it.
How can I clear that preview image?
If you try hiding the preview image than add condition like this:
<div>
{imagePreviewUrl && <img src={imagePreviewUrl} /> }
</div>
See full example by your code in the playground: https://jscomplete.com/playground/s524255