I have a code as follows,
export const FileAttachment = memo(({ url, title, className, ...messageBubbleProps }) => {
const downloadFile = (link) => {
fetch(link, {
method: 'GET'
})
.then(response => response.blob())
.then(blob => {
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = link.split('/').pop();
document.body.appendChild(a);
a.click();
a.remove();
});
}
return (
<MessageBubble className={createClassName(styles, 'file-attachment', {}, [className])} {...messageBubbleProps}>
<a click="downloadFile(url)" rel='noopener noreferrer' className={createClassName(styles, 'file-attachment__inner')}>
<FileAttachmentIcon url={url} />
<span className={createClassName(styles, 'file-attachment__title')}>{title}</span>
<DownloadIcon width={20} height={20} className={createClassName(styles, 'file-attachment__download-button')} />
</a>
</MessageBubble>
)
});
Here when I click on anchor tag it is actually not calling the downloadFile function. Although its a react project I am not sure how can I do it. Can you please help on it. Thanks.
<a onClick={() => downloadFile('url')} ... >
...
</a>
https://reactjs.org/docs/handling-events.html
Related
I have a list of items; Each item has a "Details" button beside it.
When the "Details" button it is pressed, I would like to show under the list the details of that element.
So far so good. I managed to do it. Even if it doesn't seem the best way. Now the problem is:
When I press, for the first time, a button, it shows the details of that item. But when I press again, regardless of the button, it close it. This is because I don't understand how to differentiate them. So for closing the "Details" I can just hit any button, and I don't want it.
My desired behavior would be (pseudo code):
if details_not_showing:
show_details(id==button_pressed)
else:
if details_showing == details_from_button_pressed
close_details()
else
show_details(id==button_pressed)
Hoping this make some sense, I leave you with my terrible code under this.
Imports
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
Function
function MonthArticles() {
const { user_id, year, month } = useParams();
const url =
"http://127.0.0.1:8000/api/" + user_id + "/posts/" + year + "/" + month;
const url_retrieve_author = "http://127.0.0.1:8000/api/retrieve-author";
const formdata = new FormData();
formdata.append("id", user_id);
const requestOptions = {
method: "POST",
body: formdata,
};
const [article, setArticle] = useState([]);
useEffect(() => {
fetch(url, {
method: "GET",
})
.then((res) => res.json())
.then((data) => {
setArticle(data);
});
}, []);
const [author, setAuthor] = useState([]);
useEffect(() => {
fetch(url_retrieve_author, requestOptions)
.then((res) => res.json())
.then((data) => {
setAuthor(data);
});
}, []);
const [details, setDetails] = useState(false);
const [articleId, setArticleId] = useState();
return (
<div>
<h2>
Articles writte in {year}, {month} - By{" "}
{author.map((author) => (
<div key={author.id}>{author.last_name}</div>
))}
</h2>
<h4>List of articles below:</h4>
<ul>
{article.map((article) => (
<div key={article.id}>
<li key={article.id}>
{article.title}{" "}
<button
id={article.id}
type='button'
onClick={() => [
setDetails((currentDetails) => !currentDetails),
setArticleId(article.id),
]}
>
Details
</button>
</li>
</div>
))}
</ul>
{details ? (
<div>
<h3>Showing details</h3>
{article
.filter((a) => a.id === articleId)
.map((filteredArticle) => (
<div>
<h4>Post created in: {filteredArticle.date_created}</h4>
<p>{filteredArticle.text}</p>
</div>
))}
</div>
) : null}
</div>
);
}
Thanks in advance
The main issue in your code is that details is toggled from true to false on any button click.
The solution with minimal changes would check the current articleId before toggling the details value.
onClick={() => [
setDetails((currentDetails) => !currentDetails),
setArticleId(article.id),
]}
Should be changed into:
onClick={() => {
// `details` is set to `true` if a new article is clicked.
// When pressing the button of the same article multiple
// times, the value is toggled.
setDetails(article.id !== articleId || !details);
setArticleId(article.id);
}}
There is also a solution that only uses a single state, but requires more code to change.
// `false` if no details, otherwise an article id
const [detailsId, setDetailId] = useState(false);
// Lookup the article of which details must be shown.
// Performance can be improved by using the useMemo hook.
const details = article.find(article => article.id === detailsId);
This essentially uses detailsId as a boolean. It contains false if no details must be shown. But instead of true we use the article id, to store both the fact that details must be shown, and the id of the article that must be shown.
details looks up the article based on the detailsId. This variable (not a state) helps simplify the view later on.
Your onClick handler then becomes:
onClick={() => {
// Set `detailId` to `false` if is the same as the current article id,
// otherwise set the current article id.
setDetailId(article.id !== detailId && article.id);
}}
Finally you need to update the view:
{details && (
<div>
<h3>Showing details</h3>
<div>
<h4>Post created in: {details.date_created}</h4>
<p>{details.text}</p>
</div>
</div>
)}
Just check on click if the id doesn't change then return null to hide the details, if not set the new article id to show the details.
function MonthArticles() {
const { user_id, year, month } = useParams();
const url =
"http://127.0.0.1:8000/api/" + user_id + "/posts/" + year + "/" + month;
const url_retrieve_author = "http://127.0.0.1:8000/api/retrieve-author";
const formdata = new FormData();
formdata.append("id", user_id);
const requestOptions = {
method: "POST",
body: formdata,
};
const [article, setArticle] = useState([]);
useEffect(() => {
fetch(url, {
method: "GET",
})
.then((res) => res.json())
.then((data) => {
setArticle(data);
});
}, []);
const [author, setAuthor] = useState([]);
useEffect(() => {
fetch(url_retrieve_author, requestOptions)
.then((res) => res.json())
.then((data) => {
setAuthor(data);
});
}, []);
const [details, setDetails] = useState(false);
const [articleId, setArticleId] = useState();
return (
<div>
<h2>
Articles writte in {year}, {month} - By{" "}
{author.map((author) => (
<div key={author.id}>{author.last_name}</div>
))}
</h2>
<h4>List of articles below:</h4>
<ul>
{article.map((article) => (
<div key={article.id}>
<li key={article.id}>
{article.title}{" "}
<button
id={article.id}
type='button'
onClick={() => [
setDetails((currentDetails) => !currentDetails),
setArticleId(prev => {
if (prev === articleId) return null
return article.id
}),
]}
>
Details
</button>
</li>
</div>
))}
</ul>
{details ? (
<div>
<h3>Showing details</h3>
{article
.filter((a) => a.id === articleId)
.map((filteredArticle) => (
<div>
<h4>Post created in: {filteredArticle.date_created}</h4>
<p>{filteredArticle.text}</p>
</div>
))}
</div>
) : null}
</div>
);
}
Currently, in your buttons onClick functions you do this:
onClick={() => [
setDetails((currentDetails) => !currentDetails),
setArticleId(article.id),
]}
The first line toggles whether or not the details secions is visible. All buttons currently toggle the details visibility. What you want is to only do this when it is the button corresponding to the currently displayed details.
onClick={() => {
//If no details are visible, show them
if(!details) setDetails(true);
//If details are visible, and this is the corresponding button, hide them
else if(article.id == articleId) setDetails(false);
setArticleId(article.id);
}}
I wanted to upload images and display the image which was chosen, How to display the image after choosing. this is my code, help me display the image, I made the function to post the image, I can post multiple images in one click but i can't display the image to preview before upload , i try to use file reader but cannot display and upload.
const [pImg, setPImg] = useState([]);
const [images, setImages] = useState([]);
const addImg = (ImagesPostDto) => {
const data2 = new FormData();
[...ImagesPostDto].forEach((Images) => {
data2.append("ImagesPostDto", Images);
});
Axios.post(`/shop/${shopID}/Product/${pID}/Images`, data2)
.then((res) => {
if (res.status === 200) {
setMessage({
data: `${res.data.MESSAGE}`,
type: "alert-success",
});
onShowAlert();
}
})
.catch((err) => {
setMessage({
data: `${err.response.data.MESSAGE}`,
type: "alert-danger",
});
setLoading(false);
onShowAlert();
});
};
const handleImageChange = (e) => {
e.preventDefault();
const ProductImg = e.target.files;
setPImg(ProductImg);
const reader = new FileReader();
reader.onloadend = () => {
setPImg(ProductImg);
setImages(reader.result);
};
reader.readAsDataURL(ProductImg);
};
const handleProductSubmit = (event) => {
event.preventDefault();
addImg(pImg);
};
return (
<div>
<Form>
<p>
<Label htmlFor="file">Upload images</Label>
<input
type="file"
id="file"
onChange={handleImageChange}
accept="image/png, image/jpg, image/jpeg"
multiple
/>
</p>
</Form>
<div className="">
{/* {images.length > 0 ? (
<div>
{images.map((image) => (
<p>
<img src={images} alt="" />
</p>
))}
</div>
) : null} */}
</div>
If you want to render images then, create ObjectURL from files array and set the images State then it should work fine. I have commented the code related to API call so that we can focus on rendering the selected images.You can just simply copy this code and paste it in CodeSandBox it should work fine Here is your code a bit modified:
import "./styles.css";
import { useState } from "react";
export default function App() {
const [pImg, setPImg] = useState([]);
const [images, setImages] = useState([]);
// const addImg = (ImagesPostDto) => {
// const data2 = new FormData();
// [...ImagesPostDto].forEach((Images) => {
// data2.append("ImagesPostDto", Images);
// });
// Axios.post(`/shop/${shopID}/Product/${pID}/Images`, data2)
// .then((res) => {
// if (res.status === 200) {
// setMessage({
// data: `${res.data.MESSAGE}`,
// type: "alert-success"
// });
// onShowAlert();
// }
// })
// .catch((err) => {
// setMessage({
// data: `${err.response.data.MESSAGE}`,
// type: "alert-danger"
// });
// setLoading(false);
// onShowAlert();
// });
// };
const handleImageChange = (e) => {
e.preventDefault();
console.log("event", e);
const ProductImg = [...e.target.files];
const images = ProductImg.map((image) => URL.createObjectURL(image));
console.log("images", images);
setImages(images);
};
// const handleProductSubmit = (event) => {
// event.preventDefault();
// addImg(pImg);
// };
return (
<div>
<form>
<p>
<label htmlFor="file">Upload images</label>
<input
type="file"
id="file"
onChange={handleImageChange}
accept="image/png, image/jpg, image/jpeg"
multiple
/>
</p>
</form>
<div className="">
{images.length > 0 && (
<div>
{images.map((image, index) => (
<p key={index}>
<img src={image} alt="" />
</p>
))}
</div>
)}
</div>
</div>
);
}
You need to fetch the Images from GET API and set the response in setImages than it will show right now the images variable is empty array.
As far I can understand your requirement, you need to preview the Images Either when you have selected or After uploaded.
What you can do is, whenever you are preparing the FormData or at the time of change event, you can store each selected file's ObjectURL in another state and Easily can display these images via the State.
I am using amplify to upload and download files from my S3 bucket. There are two issues i am facing
The download link tries to download a random named json blob instead of a word document that i have in the bucket.(myword.docx comes out as random_characters.json). How can i get the actual S3 object as the filename to download.
When i execute the code, there are two download links for each file in the s3 bucket. I am unable to figure out what is wrong in the code to figure out the number of download links that are generated.
Your help is really appreciated.
import Amplify from "aws-amplify";
import "./App.css";
import { withAuthenticator } from "#aws-amplify/ui-react";
import "#aws-amplify/ui-react/styles.css";
import { Storage } from "aws-amplify";
import awsExports from "./aws-exports";
import { useEffect, useState } from "react";
Amplify.configure(awsExports);
function App({ signOut, user, sub}) {
const [fileData, setFileData] = useState();
const [fileStatus, setFileStatus] = useState(false);
const [s3DownloadLinks, setS3DownloadLinks] = useState([]);
const uploadFile = async () => {
const result = await Storage.put(fileData.name, fileData, {
level: "private",
contentType: fileData.type,
identityId: sub
});
setFileStatus(true);
console.log(21, result);
};
async function listObjectsFromS3() {
const s3Objects = await Storage.list('', {
level: "private",
//contentType: fileData.type,
identityId: sub
});
s3Objects.map(async (item) => {
console.log(30, item);
let downloadLink = await generateDownloadLinks(item.key);
console.log(30, downloadLink);
setS3DownloadLinks((s3DownloadLinks) => [
...s3DownloadLinks,
downloadLink,
]);
console.log(31, s3DownloadLinks);
});
}
async function generateDownloadLinks(fileKey) {
const result = await Storage.get(fileKey, {
level: 'private',
identityId: sub,
download: true
});
console.log(32, result);
return downloadBlob(result.Body, "filename");
}
async function downloadBlob(blob, filename) {
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = "output.docx";
return a;
}
useEffect(() => {
listObjectsFromS3();
}, []);
return (
<div className="App">
<h1>Hello {user.username}</h1>
<div>
<input type="file" onChange={(e) => setFileData(e.target.files[0])} />
</div>
<div>
<button onClick={uploadFile}>Upload file</button>
</div>
{fileStatus ? "File uploaded successfully" : ""}
<div id="demo" >
<h2> Your Transcribed outputs are available here</h2>
</div>
{/* List all s3 objects and download by clicking on the link */}
{s3DownloadLinks.map((item, index) => (
<div key={index}>
<a href={item} target="_blank" download="">
Link {index}
</a>
</div>
))}
<div><button onClick={signOut}>Sign out</button></div>
</div>
);
}
export default withAuthenticator(App);
Ill be changing the key shortly. Using the code below I should be able to load a list of movies from the API and each movie should be linked to it's Provider Link website. using
the upMovieDetail. can anyone help point me in the right direction? I have a feeling it has something to do with the component being re-renderd after the click?
here is the codesandbox if you'd rather try to fix it here.. --
https://codesandbox.io/s/movieapp-searchbar-forked-qv1o6
const key ="fde5ddeba3b7dec3fc1f51852ca0fb95";
const getUpMovieDetail = (movieId) => {
//const [movieId, setMovieId] = useState([]);
const url = `https://api.themoviedb.org/3/movie/${movieId}/watch/providers?api_key=${key}`;
return fetch(url);
};
function UpMovieDetail({ movieItem }) {
const [searchLink, setSearchLink] = useState(null);
useEffect(() => {
getUpMovieDetail(movieItem.id)
.then((res) => res.json())
.then((res) => {
setSearchLink(res?.results?.US?.link);
});
}, [movieItem.id]);
return (
<ul className="flexed-search">
{searchLink.map((item) =>
<div className="poster-container" key={item.id}>
<li className="list-item">
<a target="_blank" rel="noopener noreferrer" href={searchLink}
onclick={((event) => {event.preventDefault()})}>
<img className="image-element" tabIndex="0" alt="movie poster"
title={`--Title: ${item.title}-- --Description:
${item.overview}-- --Vote Average: ${item.vote_average}`}
aria-label={item.title}
src={`https://image.tmdb.org/t/p/w500${item.poster_path}`} />
</a>
<h3 className="posterTitle">{item.title}</h3>
</li>
</div>
)}
</ul>
);
};
const SearchBar = () => {
const [search, setSearch] = useState([]);
const [input, setInput] = useState('');
// Input Field
const onUserInput = ({target}) => {
setInput(target.value);
};
// Api Call
const SearchApi = (event) => {
const aUrl = "https://api.themoviedb.org/3/search/movie?api_key=fde5ddeba3b7dec3fc1f51852ca0fb95";
const newUrl = aUrl +'&query=' + input;
event.preventDefault();
fetch(newUrl)
.then((response) => response.json())
.then((data) => {
setSearch(data.results);
})
.catch((error) => {
console.log('Error!! Data interupted!:', error)
})
};
return (
// Heading
<div>
<div className="container">
<h1>Movie Search Extravaganza!</h1>
{/* Input Field and Button Form */}
<form onSubmit={SearchApi}>
<input value={input} onChange={onUserInput} type="text" className="searchbar" aria-label="searchbar" placeholder="search" required/>
<br></br>
<button type="submit" aria-label="searchbutton" className="searchBtn">Movie Express Search</button>
</form>
<h1 className="row-label" tabIndex="0">Movies Related To Your Search</h1>
</div>
<div className="byName-container">
{search.map((item) => (
<UpMovieDetail key={item.id} movieItem={item} />
))}
</div>
</div>
)};
export default SearchBar;```
[1]: http://codesandbox.io/s/movieapp-searchbar-forked-qv1o6
[2]: https://codesandbox.io/s/movieapp-searchbar-forked-qv1o6
From the first render it throws the error because searchLink is null.
Try this:
{
searchLink && searchLink.length && searchLink.map((item) =>
...
}
I was following one of the tutorials with React js when this isuue came up. I am using Cloudinary React SDK (React image and video upload). I am using their Upload Widget. But when I press the button to open the widget it gives me this error - 'TypeError: Cannot read property 'createUploadWidget' of undefined'
Here's the script src - <script src="https://widget.cloudinary.com/v2.0/global/all.js" type="text/javascript" ></script>
Here's the code of App.js
import React, { useState } from "react";
import "./App.css";
export default function App() {
const [imageUrl, setimageUrl] = useState(null);
const [imageAlt, setimageAlt] = useState(null);
const handleImageUpload = () => {
const { files } = document.querySelector('input[type="file"]');
const imageFile = document.querySelector('input[type="file"]');
// destructure the files array from the resulting object
const filesa = imageFile.files;
// log the result to the console
console.log("Image file", filesa[0]);
const formData = new FormData();
formData.append("file", files[0]);
// replace this with your upload preset name
formData.append("upload_preset", "xxxxxxx");
const options = {
method: "POST",
body: formData,
};
// replace cloudname with your Cloudinary cloud_name
return fetch(
"https://api.Cloudinary.com/v1_1/xxxxxx/image/upload",
options
)
.then((res) => res.json())
.then((res) => {
setimageUrl(res.secure_url);
setimageAlt(`An image of ${res.original_filename}`);
})
.catch((err) => console.log(err));
};
const openWidget = () => {
// create the widget
const widget = window.Cloudinary.createUploadWidget(
{
cloudName: "xxxxxx",
uploadPreset: "xxxxx",
},
(error, result) => {
if (result.event === "success") {
setimageUrl(result.info.secure_url);
setimageAlt(`An image of ${result.info.original_filename}`);
}
}
);
widget.open(); // open up the widget after creation
};
return (
<div className="app">
<section className="left-side">
<form>
<div className="form-group">
<input type="file" />
</div>
<button type="button" className="btn" onClick={handleImageUpload}>
Submit
</button>
<button type="button" className="btn widget-btn" onClick={openWidget}>
Upload Via Widget
</button>
</form>
</section>
<section className="right-side">
<p>The resulting image will be displayed here</p>
{imageUrl && (
<img src={imageUrl} alt={imageAlt} className="displayed-image" />
)}
</section>
</div>
);
}
Any help is greatly appreciated !
Cloudinary should be referenced in a lowercase -
...
const widget = window.cloudinary.createUploadWidget(
...