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
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;
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)}
/>
I'm working on a small react project, and I'm trying to map over an array, and display the names (in react).
I've been trying to find a solution to this error (.map is not a function), and the only suggestion I found was adding the index as a key. It doesn't seem to be solving it, any help would be greatly appreciated!
Your groups is not an array hence the .map is not a part of the groups. I tried your code sandbox and the groups is having following value.
3 people should be one group!
Check how is your groups being set.
for the workaround until you fix your group value you can do something like this.
import React, { useState } from "react";
import { shuffleArray, createGroups } from "./utils";
import "./styles.css";
//TODO groups should be at least 3 people
//MAYBE also add option people per group
const App = () => {
const [names, setNames] = useState("");
const [numGroup, setNumGroup] = useState(2);
const handleNames = (e) => {
e.preventDefault();
};
const shuffledArr = shuffleArray(names.split(" "));
const groups = createGroups(numGroup, shuffledArr);
return (
<div className="App">
<h1>Coffee Groups</h1>
<form className="form" onSubmit={handleNames}>
<textarea
name="listOfNames"
type="text"
value={names}
onChange={(e) => setNames(e.target.value)}
/>
<div className="button">
<label>groups</label>
<input
min="2"
className="numInput"
type="number"
value={numGroup}
onChange={(e) => setNumGroup(e.target.value)}
/>
<button type="submit" onClick={() => console.log(groups)}>
Run
</button>
</div>
</form>
<div>
{Array.isArray(groups) && groups.map((group, idx) => {
return (
<ul key={idx}>
<li>Group {idx + 1}</li>
{group.map((person, idx) => {
return <li key={idx}>{person}</li>;
})}
</ul>
);
})}
</div>
</div>
);
};
export default App;
On codesandbox, if you hover over line 16 const groups = createGroups(numGroup, shuffledArr); you'll see that groups is any[] | "3 people should be one group". Since in some cases groups is not an array, line 43 JS complains that .map() does not exist on a string.
You can fix this by making createGroups only returning an array, and then instead of groups? try groups.length >= 3?
I have a UserLists component where the user types into an input and adds the value onto the bottom of the screen.
The input value is added into the whitelist state. The state is then mapped and creates more inputs where the user can decide to delete said input or edit it.
I am having trouble deleting the inputs. I thought I could delete each input individually by splicing the state, but my implementation of the deleteItem deletes multiple inputs when any single one of them is clicked.
I also cannot edit any of the inputs because their value is set by my addItem function.
import React, { useEffect, useState } from "react";
export const UserLists = () => {
const [whitelist, setWhiteList] = useState([]);
const addItem = () => {
let newValue = document.getElementById("whiteList").value;
setWhiteList([...whitelist, newValue]);
};
useEffect(() => {
console.log(whitelist, "item changed");
}, [whitelist]);
const deleteItem = (index) => {
let test = whitelist.splice(index, 1);
setWhiteList(test);
console.log("index:", index);
};
const editItem = () => {};
return (
<div>
<h2>WhiteList</h2>
<input id="whiteList" type="text" />
<button onClick={addItem}>Add</button>
{whitelist.map((item, index) => {
return (
<div>
<input type="text" value={item} onChange={editItem} />
<button onClick={() => deleteItem(index)}>Delete</button>
<p>{index}</p>
</div>
);
})}
</div>
);
};
How can I revise my code to successfully individually delete and edit inputs?
My codesandbox
You need to change your editItem and deleteItem functions in order to make your edit and delete functionality work properly. Here's a code sandbox link to the solution to your problem:
https://codesandbox.io/s/whitelist-forked-y51w8
Don't do:
let test = whitelist.slice(index, 1);
setWhiteList(test);
Do this instead:
whitelist.splice(index, 1);
setWhiteList([...whitelist]);
I actually have two questions here, the first is about my code. the second is about how to upload mp3 files to the Firestore cloud data base through react.
The following code should be a form that adds new documents easily inside the cloud storage collection:
import React, {useState} from 'react';
import db from '../index';
const AddSongs = () => {
const [song, setSong] = useState('');
const [artist, setArtist] = useState('');
const [src, setSrc] = useState('');
const inSubmit = (e) =>{
e.preventDefault();
let songBase = db.collection('songs');
let data = {song, artist, src}
songBase
.add(data)
}
return (
<div>
<form onSubmit={inSubmit}>
<label>
Add Your Track
</label>
<input type="text" placeholder="Name of the track" value={song} onChange={e =>
setSong(e.target.value)} />
<input type="text" placeholder="Name of the artist" value={artist} onChange={e =>
setArtist(e.target.value)} />
<input type="file" value={src} onChange={e => setSrc(e.target.value)} />
<div>
<input type="submit" value="add it" />
</div>
</form>
</div>
);
}
export default AddSongs;
and the docs are added to the cloud storage successfully as normal docs with properties which are song, artist and src.
and this is another code which should then take the current data inside the collection and renders it to a jsx div
import React, {useState} from 'react';
import db from '../index';
const SongList = () => {
const [list, setList] = useState([])
let songbase = db.collection('songs')
songbase.onSnapshot(snapshot =>{
snapshot.docs.forEach(doc =>{
setList([...list, {song: doc.song, artist: doc.artist, src: doc.src, id: Math.random(0,1)}])
})
})
const renderedList = list.length ? (list.map(songItem =>{
return(
<div key={songItem.id}>
<span>{songItem.song}</span>
<audio controls>
<source src={songItem.src} type="audio/mpeg" />
</audio>
<span>{songItem.artist}</span>
</div>
)
})) : (<h1>nope !</h1>)
return (
<div>
{renderedList}
</div>
);
}
export default SongList;
And then what happens here is that the songItem (that gets rendered inside an empty jsx) repeats itself endlessly and keeps scrolling down.. !
The second question is how to add mp3 files to the firebase cloud storage from my local device, because the (src) adds the mp3 file path as a string in the doc properties, example : C:\fakepath\Black Sabbath - Falling off the Edge of the World (With Lyrics).mp3.
Sorry for the long question, your help would be highly appreciated.
Try using react-firebase-file-uploader package.
It consist of props like storageRef - A reference to the firebase storage folder, where the file should be saved.
onUploadStart, onProgress, onUploadSuccess, onUploadError, filename, metadata, randomizeFilename, maxHeight, maxWidth and many other useful props.
Here's the reference link for more details. Click here