I have the following code where a user can select a file and hit upload and the Choose button is disabled.
Code Sanbox link is here:
import "primeicons/primeicons.css";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.css";
import "primeflex/primeflex.css";
import "../../index.css";
import ReactDOM from "react-dom";
import React, { useRef, useState } from "react";
import { FileUpload } from "primereact/fileupload";
export const FileUploadDemo = () => {
const toast = useRef(null);
const [disableButton, setDisableButton] = useState(false);
const onUpload = () => {
toast.current.show({
severity: "info",
summary: "Success",
detail: "File Uploaded"
});
};
const onTemplateAdvancedSelect = (e) => {
console.log("Printing onTemplateAdvancedSelect ");
console.log(e);
let inputFileType = document.querySelector("input[type=file]");
//setDisableButton(true);
inputFileType.classList.add("toDisableOnSelect");
inputFileType.disabled = true;
let htmlCollection = document.getElementsByClassName(
"p-button p-component p-button-icon-only"
);
console.log("Printing htmlCollection");
console.log(htmlCollection.length);
console.log(htmlCollection);
// htmlCollection.addEventListener("click", function () {
// inputFileType.disabled = false;
// });
//console.log(htmlCollection.item(19));
};
return (
<div>
<div className="card">
<h5>Advanced</h5>
<FileUpload
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
/>
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<FileUploadDemo />, rootElement);
So Inside onTemplateAdvancedSelect function, I want to set inputFileType.disabled to false once user hits the cross icon as shown below:
I don't want to use getElementByClassName just like I have attempted to use in my code of above function. What would be a better way to achieve my goal?
Primereact version I'm using : 4.2.2
One solution is customizing the row item of the selected file.
so the itemTemplate prop is exactly for that goal.
export const FileUploadDemo = () => {
const toast = useRef(null);
const fileUploadRef = useRef(null) // reference to uploader node
const [disableButton, setDisableButton] = useState(false);
const onTemplateRemove = (file, callback) => {
// setTotalSize(totalSize - file.size);
const domInput = fileUploadRef.current.fileInput;
domInput.disabled = false;
callback();
}
const onTemplateAdvancedSelect = () => {
const domInput = fileUploadRef.current.fileInput; // pure dome element
domInput.disabled = true;
}
const itemTemplate = (file, props) => {
return (
<>
<div>
<img alt={file.name} role="presentation" src={file.src} width="50" />
</div>
<div class="p-fileupload-filename">{file.name}</div>
<div>{file.size}</div>
<div>
{ /* here you have access to that button */}
<button
type="button"
class="p-button p-component p-button-icon-only"
onClick={() => onTemplateRemove(file, props.onRemove)}>
<span class="p-button-icon p-c pi pi-times"></span>
<span class="p-button-label p-c"> </span>
</button>
</div>
</>
)
}
return (
<div>
<div className="card">
<h5>Advanced</h5>
<FileUpload
ref={fileUploadRef} // pass a reference for `input` manipulation
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
/* here we should pass the customized template as prop */
itemTemplate={itemTemplate}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
/>
</div>
</div>
);
Please use useRef hook provided by react. Example below:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
I'm trying to understand your use case. Do you want to remove the image from the upload stack by clicking the x button?
I think you should rather use the built-in API to trigger functionality. If that doesn't help you can extend the library.
You don't need to do a getElementsByClassName. You can use the onRemove event to handle the file remove event.
<FileUpload
multiple={false}
name="demo[]"
url="https://primefaces.org/primereact/showcase/upload.php"
onUpload={onUpload}
id={"myId"}
accept="image/*"
maxFileSize={1000000}
onSelect={onTemplateAdvancedSelect}
disabled={disableButton}
emptyTemplate={
<p className="p-m-0">Drag and drop files to here to upload.</p>
}
onRemove={(e, file) => setDisableButton(false)}
/>
Related
Here is the relevant code:
const Members = () => {
// array of each video in selected grade
const videosMap = (videos) => {
return videos.map((video) => (
<VideoCard
key={video.id}
thumbnail={video.thumbnail}
title={video.title}
description={video.description}
onClick={() => {
handleVideoClick();
}}
/>
));
};
// updates state of shown videos & page heading
const handleGradeButtonClick = (videos, heading) => {
setShowVideos(videosMap(videos));
setVideosHeading(heading);
};
const handleVideoClick = () => {
console.log("test");
};
// controls state of which grade's videos to show
const [showVideos, setShowVideos] = useState(videosMap(kinder_videos));
// controls states heading to display depending on selected grade
const [videosHeading, setVideosHeading] = useState("Kindergarten");
const [showVideoDetails, setShowVideoDetails] = useState(null);
The handleVideoClick is the function that is not working when I click on one of the mapped VideoCard components.
Here is the full code if you want to see that:
https://github.com/dblinkhorn/steam-lab/blob/main/src/components/pages/Members.js
When I look in React DevTools at one of the VideoCard components, it shows the following:
onClick: *f* onClick() {}
If I don't wrap it in an arrow function it does execute, but on component load instead of on click. I have a feeling it has something to do with my use of .map to render this component, but haven't been able to figure it out.
Thanks for any help!
There's no problem with your mapping method, you just need to pass the onClick method as a prop to your VideoCard component :
On your VideoCard component do this :
const VideoCard = (props) => {
const { thumbnail, description, title, onClick } = props;
return (
<div className="video-card__container" onClick={onClick}>
<div className="video-card__thumbnail">
<img src={thumbnail} />
</div>
<div className="video-card__description">
<div className="video-card__title">
<h3>{title}</h3>
</div>
<div className="video-card__text">{description}</div>
</div>
</div>
);
};
export default VideoCard;
Please review my code first.
const test = () => {
const [files, setFiles] = useState ([]);
//I think I have to edit following statement.
const handleFile = (e) => {
const newFiles = []
for (let i=0; i < e.target.files.length; i++) {
newFiles.push(e.target.files[i])
}
setFiles(newFiles)
};
return (
{files.map((file, index) => (
<div>
<div key={index}>
<p>
{file.name}
</p>
<Button size='small' onClick={() => {deleteSelectedFile(file.name)}}>
Delete
</Button>
</div>
</div>
))}
<div>
<label onChange={handleFile}>
<input type='file' multiple />+ Attach File
</label>
</div>
)
}
with handleFile statement, I can appropriately get the files.
However, when I upload one file at a time and then upload one file again,
the file is not added. the file replaces file.
There is not problem when I upload multiple time at once.
For example, I upload 'hello.jpg', and it renders well in the screen. Then, I upload 'goodbye.jpg', it renders well, but 'goodbye.jpg' replaces 'hello.jpg'.
Therefore, what I see is just 'goodbye.jpg' [button], not
'hello.jpg' [button]
'goodbye.jpg' [button]
.
I want my files to stacked up, without replacing.
I need some wisdom!
In my opinion, you only need to spread the prev state values and the files during the change event.
import { useState } from "react";
export default function App() {
const [files, setFiles] = useState([]);
const handleChange = (e) => {
// This is what you need
setFiles((prev) => [...prev, ...Object.values(e.target.files)]);
};
return (
<div>
<label onChange={handleChange}>
<input type="file" multiple />
</label>
{files.map((file) => {
return <p>{file.name}</p>;
})}
</div>
);
}
how about you don't create a new variable for new files, you just set the state for the files since it's an array
setFiles(oldFile => [...oldFile,e.target.files[i]]);
If it's possible you can drop a codepen link
I'm using React JS and have faced a problem.
I have a component on the page which has some inputs. When user clicks on any input a new block should be created below and the same input has to be focused at the same time.
Everything worked until I've created a show logic:
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
After that I get ×
TypeError: Cannot read property 'focus' of null
Below is my Main component where everything on the page happens.
const Sales = () => {
const [isTouched, setIsTouched] = useState(false);
const textInput = useRef(null);
function handleInput() {
setIsTouched(true);
textInput.current.focus();
}
const readyBlock = isTouched ? <ViewModule textInput={textInput}/> : null;
return (
<main className="sales-page">
<div className="main__title">
<h2 className="main__heading">Bonuses</h2>
</div>
<div className="content-container">
<UploadForm>
<FileUploadInput
handleChange={handleInput}
placeholder="Header"/>
<FileUploadTextArea placeholder="Descr"/>
</UploadForm>
</div>
<div className="ready-container ">
{readyBlock}
</div>
</main>
)
}
const ViewModule = ({textInput}) => {
return (
<UploadForm classNames="textarea-written">
<FileUploadInput
ref={textInput}
placeholder="Заголовок"/>
<FileUploadTextArea placeholder="Descr"/>
<div className="btn-container">
<Btn classNames="cancel-btn">Cancel</Btn>
<Btn>Save</Btn>
</div>
</UploadForm>
)
}
Below is an input component:
const FileUploadInput = React.forwardRef((props, ref) => {
return (
<div className="text-input-wrapper">
<input
ref={ref}
type="text"
id="file-text-input"
name="file__upload-title"
placeholder={props.placeholder}
onClick={props.handleChange} />
</div>
)
});
I assume you want to focus the ViewModule component when its added.
The problem is that the Ref textInput is not assigned to any component before the ViewModule is added to the DOM tree. You would have to first add the ViewModule to the DOM tree on state change and then later in a useEffect hook you will find the textInput Ref properly assigned.
function handleInput() {
setIsTouched(true);
}
useEffect(() => {
if (textInput.current === null) return
if (isTouched) textInput.current.focus();
}, [isTouched])
Also you should pass the textInput Ref to ViewModule using React.forwardRef as you did for FileUploadInput.
const ViewModule = React.forwardRef((ref) => {...});
And use it like this.
const readyBlock = isTouched ? <ViewModule ref={textInput}/> : null;
So here is the problem which I can't seem to solve. I have an app component, inside of App I have rendered the Show Component. Show component has toggle functionality as well as a outside Click Logic. In the Show component I have a Button which removes an item based on his Id, problem is that When I click on the button Remove. It removes the item but it also closes the Show Component, I don't want that, I want when I press on button it removes the item but does not close the component. Thanks
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
Show Component
import React, { useContext } from 'react'
import './Show.css'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
import { GrClose } from 'react-icons/gr'
const Show = ({closeShow}) => {
const {remove, icons }= useGlobalContext()
}
return (
<div className='control__Panel'>
<div className='close__cont'>
<GrClose className='close' onClick={closeShow} />
<h3>Show</h3>
</div>
<div className='control__cont'>
{icons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
})}
</div>
</div>
)
}
export default Show
Try stop propagation function, it should stop the propagation of the click event
<button
className='unin__button'
onClick={(e) => {
e.stopPropagation();
remove(id);
}}
>remove</button>
You have a few typos in your example. Are they in your code? If they are, you're always reach the closeShow() case in your handler, since you're using the wrong ref.
const App =()=>{
const[isShowOpen, setIsShowOpen]=React.useState(false) <<-- Here 'isShowlOpen'
const show = useRef(null) <<-- here 'S'how
function openShow(){
setIsShowOpen(true)
}
function closeShow(){ <<-- Here 'closeShowl'
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !show.current.contains(e.target)){ <<-- here 'showl'
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
Using react-quill. I want to add a non-editable block of text, I am able to create the blot, but if I try to add a contenteditable=false attribute to it, it does not work. My code is as follows
import ReactQuill from 'react-quill';
import './App.scss';
import 'react-quill/dist/quill.snow.css';
import { useState, useRef } from 'react';
const Quill = ReactQuill.Quill;
const BlockEmbed = Quill.import('blots/embed');
class Mention extends BlockEmbed {
static create(value) {
let node = super.create(value);
node.innerText = value;
// node.contenteditable = false;
node.setAttribute("contenteditable", false);
return node;
}
static value(node) {
return node.childNodes[0].textContent;
}
}
Mention.blotName = 'label';
Mention.tagName = 'SPAN';
Mention.className = 'ql-label';
Quill.register(Mention);
function App() {
const [value, setValue] = useState('');
const thisEditor = useRef(null);
const inserMention = (thisEditor) => {
const editor = thisEditor.getEditor();
let range = editor.getSelection();
let position = range ? range.index : 0;
editor.insertEmbed(position, 'label');
}
return (
<div className="container bg-crow-green bg-gradient px-0">
<div className='mt-4 border rounded'>
<ReactQuill ref={thisEditor} theme='snow' value={value} onChange={setValue} />
<button type="button" className="btn mt-4 btn-danger w-25" onClick={() => inserMention(thisEditor.current)}>Insert</button>
</div>
</div >
);
}
export default App;
Clicking the button Insert, creates a new Embed and adds it to the editor, but it is editable which i do not want. Another problem is I want to reference the newly added embed and later on change its value too, I can do this using Parment.find() and later use format but I cannot figure out how to do this in react.
Try using camcelCase property.
node.setAttribute("contentEditable", false);