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
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);
};
I'm using a NASA API to render media images and info on a page. I want to add a button that "saves" the image to a favorites array. How would I do this? Right now, I have it so that when a button is clicked, the whole array is duplicated instead of a single object from the API.
import React, { useState, useEffect } from "react";
const apiKey = process.env.REACT_APP_APOD_KEY;
function Main() {
const [media, setMedia] = useState([]);
const [faves, setFaves] = useState([]);
const mediaGet = () => {
fetch(`https://api.nasa.gov/planetary/apod?api_key=${apiKey}&count=10`)
.then(res => res.json())
.then(result => {
setMedia(result)
})
}
const addFave = (media) => {
const newFavesList = [...faves, media];
setFaves(newFavesList);
// make this function add to faves array (new array)
};
useEffect(() => {
mediaGet()
}, [])
console.log(media);
return (
<>
<h1>Clever name here</h1>
{/* move this to Card component */}
{media.map((media) => (
<div key={media.id}>
<h2>{media.title}</h2>
<h3>{media.date}</h3>
<img src={media.url} alt={media.title} />
<button onClick={addFave} type="button">Add to array</button>
</div>
))}
</>
)
}
export default Main;
Your addFave function requires an argument (media) but you are calling it with onClick={addFave} which will only pass the click Event when called.
To pass the media object from your map callback, use
onClick={() => addFave(media)}
I would also change the handler to use the functional version of the useState setter
const addFave = (fave) => {
setFaves(prev => [...prev, fave])
}
FYI, try to avoid shadowing (re-using variable names in different scopes). There's even a handy ES-lint rule for this ~ https://eslint.org/docs/rules/no-shadow
{media.map(item => (
<div key={item.id}>
<h2>{item.title}</h2>
<h3>{item.date}</h3>
<img src={item.url} alt={item.title} />
<button onClick={() => addFave(item)} type="button">Add to array</button>
</div>
))}
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
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
I have a INPUT BUTTON and INPUT FILE, I want to click the BUTTON and it will trigger the INPUT FILE event in REACT JS.
React.createElement('input',{type:'file', name:'myfile'})
then the button
React.createElement('a',{onClick: this.doClick},'Select File')
So how to define and trigger the INPUT FILE click event when we click the A HREF?
Your help is appreciate.
:-)
Update: Sep 18, 2021
Note: On NextJS, I was facing onChange event is not trigged from input file element. For that, we can use onInputCapture or onChangeCapture. For more detailed information, Stackoverflow - onChange event is not firing
Basic example on onChangeCapture as per our requirement. Requires React ^16.8,
const Dummy = () => {
const inputFileRef = React.useRef();
const onFileChangeCapture = ( e: React.ChangeEvent<HTMLInputElement> ) {
/*Selected files data can be collected here.*/
console.log(e.target.files);
};
const onBtnClick = () => {
/*Collecting node-element and performing click*/
inputFileRef.current.click();
};
return (
<form>
<input
type="file"
ref={inputFileRef}
onChangeCapture={onFileChangeCapture}
/>
<button onClick={onBtnClick}>Select file</button>
</form>
);
};
Using useRef Hook in functional components. Requires React ^16.8,
const Dummy = () => {
const inputFileRef = useRef( null );
const onFilechange = ( e ) => {
/*Selected files data can be collected here.*/
console.log( e.target.files );
}
const onBtnClick = () => {
/*Collecting node-element and performing click*/
inputFileRef.current.click();
}
return (
<form className="some-container">
<input
type="file"
ref={inputFileRef}
onChange={onFileChange}
/>
<button onClick={onBtnClick}>Select file</button>
</form>
)
}
Class Implementation with React.createRef() and handling click with node element.
class Dummy extends React.Component {
constructor( props ) {
super( props );
this.inputFileRef = React.createRef();
this.onFileChange = this.handleFileChange.bind( this );
this.onBtnClick = this.handleBtnClick.bind( this );
}
handleFileChange( e ) {
/*Selected files data can be collected here.*/
console.log( e.target.files );
}
handleBtnClick() {
/*Collecting node-element and performing click*/
this.inputFileRef.current.click();
}
render() {
return (
<form className="some-container">
<input
type="file"
ref={this.inputFileRef}
onChange={this.onFileChange}
/>
<button onClick={this.onBtnClick}>Select file</button>
</form>
)
}
}
You don't need jQuery for this. You don't even need an event handler. HTML has a specific element for this, called label.
First, make sure your input element has an id attribute:
React.createElement('input',{type:'file', name:'myfile', id:'myfile'})
Then, instead of:
React.createElement('a',{onClick: this.doClick},'Select File')
Try:
React.createElement('label',{htmlFor: 'myfile'},'Select File')
(Instead of adding htmlFor and id attributes, another solution is to make the input element a child of the label.)
Now clicking the label should trigger the same behaviour as clicking the input itself.
You could trigger the input type file with ref, f.e:
on your class component:
<input
ref={fileInput => this.fileInput = fileInput}
type="file"
/>
<button onClick={this.triggerInputFile}> Select File </button>
and make a function on that class component too:
triggerInputFile = () => this.fileInput.click()
Using Hooks with useref:
import React, {useRef} from 'react';
const FancyInput = () => {
const fileInput = useRef(null)
const handleClick = () => {
fileInput.current.click()
}
const handleFileChange = event => {
console.log("Make something")
}
return(
<div className="patientactions-container">
<input
type="file"
onChange={(e) => handleFileChange(e)}
ref={fileInput}
/>
<div onClick={() => handleClick()}></div>
</div>
)
}
export default FancyInput;
Building on the answer from #YÒGÎ , here is an implementation using TypeScript:
class Dummy extends React.Component {
fileInputRef: React.RefObject<HTMLInputElement> = React.createRef();
forwardClickToInputElement = () => {
this.fileInputRef.current!.click();
};
handleUploadDemand = (ie: ChangeEvent<HTMLInputElement>) => {
const fileList: FileList = ie.target.files;
// do something with the FileList, for example:
const fileReader = new FileReader();
fileReader.onload = () => {
const str = String(fileReader.result);
try {
const parsedContent = YOUR_OWN_PARSING(str);
} catch (error) {
// YOUR OWN ERROR HANDLING
}
};
fileReader.readAsBinaryString(fileList[0])
}
render() {
return (
<div className="some-container">
<button onClick={this.forwardClickToInputElement}>Select File</button>
<input ref={this.fileInputRef} type="file" onChange={this.handleSelectFile} hidden={true}/>
</div>
)
}
}
References:
Solution for how to use refs in React with Typescript https://stackoverflow.com/a/50505931/2848676
Use ! operator for ref type narrowing https://medium.com/#martin_hotell/react-refs-with-typescript-a32d56c4d315
const CustomInput = () => {
const handleClick = () => {
document.getElementById("file_upload").click();
};
const handleFileChange = (event) => {
console.log("Make something");
};
return (
<div className="patientactions-container">
<input type="file" id="file_upload" onChange={(e) => handleFileChange(e)} />
<div onClick={() => handleClick()}></div>
</div>
);
};
export default CustomInput;
EDIT: This is a question I answered a long time ago not knowing very much react at this time. The fun thing is that it has been considered valid ^^.
So for anyone reading this answer; this answer is wrong and is a very good example of something you shouldn't do in react.
Please find below a nice anti-pattern, again, don't do it.
=================================================
You can achieve this using jQuery:
this.doClick: function() {
$('input[type=file]').trigger('click');
}
React does not provide specific functions to trigger events, you can use jQuery or simply native Javascript: see Creating and triggering events on MDN