Weird behaviour with React Hooks and FileReader - html

I'm new to React Hooks and honestly I'm not sure if this problem is related to Hooks or if I'm just doing something generally wrong.
I want to build a image uploader comonent that uses the HTML5 FileReader in order to show users the uploaded images before actually POSTing them.
Below is what I have so far.
Basically <div id="from-effect"></div> is currently my way of checking whether the images could be rendered.
I first wanted to fill this <div> without side effects (like <div>I have {files.length} files</div>) but this didn't react to changes at all.
The solution below with useEffect is reacting to changes.
However, if you try uploading a few images you will notice that quite often it's showing wrong results.
function FileUploader(props) {
const [files, setFiles] = useState([]);
const loadImageContent = (name, newFiles) => {
return (e) => {
newFiles.push({ name: name, src: e.target.result });
};
}
const handleUpload = async (e) => {
const newFiles = [];
for (const file of e.target.files) {
const reader = new FileReader();
reader.onload = loadImageContent(file.name, newFiles);
await reader.readAsDataURL(file);
}
setFiles(newFiles);
}
useEffect(() => {
console.log('in use Effect, files:', files);
const prevCont = document.getElementById("from-effect");
prevCont.innerHTML = `I have ${files.length} files`;
});
return <div>
<input
type="file" name="fileUploader" id="fileUploader"
accept="image/*" multiple="multiple"
onChange={handleUpload}
/>
<div id="from-effect"></div>
</div>;
}
What am I doing wrong?
Or even better, how can I implement this without side effects?

I am not sure I follow your ultimate goal, or what you mean when you say you want to show users the uploaded images before POSTing them - do you want to POST automatically, or do you want the user to click an "upload/save/POST" button or something?
Here is an example of how to display images:
Edit: made things a little more clear, added "save" button which shows an alert that contains data you could possibly use to POST back to your server. Also, added a method to "JSONify" the file metadata, since the way we are uploading files does not let us natively convert [object File] into JSON.
const { useState } = React;
function FileUploader(props) {
const [files, setFiles] = useState([]);
const getFileMetadata = file => {
/**
* The way we are handling uploads does not allow us to
* turn the uploaded [object File] into JSON.
*
* Therefore, we have to write our own "toJSON()" method.
*/
return {
lastModified: file.lastModified,
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath
}
}
const handleUpload = e => {
let newstate = [];
for (let i = 0; i < e.target.files.length; i++) {
let file = e.target.files[i];
let metadata = getFileMetadata(file);
let url = URL.createObjectURL(file);
newstate = [...newstate, { url, metadata }];
}
setFiles(newstate);
};
const handleSave = () => {
alert(`POST Files Here..\n\n ${JSON.stringify(files,null,2)}`);
}
return (
<div>
<input type="file" accept="image/*" multiple onChange={handleUpload} />
<div>
<button onClick={handleSave} disabled={!(files && files.length > 0)}>
Save Image(s)
</button>
</div>
{files.map(f => {
return (
<div>
<img src={f.url} height="100" width="100" />
</div>
);
})}
</div>
);
}
ReactDOM.render(<FileUploader />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Related

Split string and add extra word at onChange in Reactjs

I am trying to show on my page spotify links submitted by users. The problem is the standard Spotify share link is missing the 'embed' in part of the url that is needed to render it so I have been trying to use 'split' to adjust the url to an embed-able one, however I just cannot get it to work?
This is the function I am using to split the url and add the extra embed text
function spotify () {
const message = "urlInput";
let split = message.split(".com/");
let joined = split[0]+".com/embed/"+split[1];
}
This is the relevant part of html code I am using to get the users input
{
currentAccount ? (<textarea
placeholder={spotifyLink}
type="url"
id="urlInput"
value={messageValue}
onChange={e => {setMessageValue(e.target.value); {spotify}}} />) : null
}
<button className="waveButton" onClick={music}>
Submit
</button>
and the function attached to the button onClick
const music = async () => {
try {
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const musicPortalContract = new ethers.Contract(contractAddress, contractABI, signer);
let count = await musicPortalContract.getTotalSongs();
console.log("Retrieved total song count...", count.toNumber());
const musicTxn = await musicPortalContract.music(messageValue);
await musicTxn.wait();
count = await musicPortalContract.getTotalSongs();
console.log("Retrieved total song count...", count.toNumber());
} else {
console.log("Ethereum object doesn't exist!");
}
} catch (error) {
console.log(error);
}
}
I would like to transform the url from this:
https://open.spotify.com/track/3u5N55tHf7hXATSQrjBh2q?si=8fe4896e171e4991
to this:
https://open.spotify.com/embed/track/46q5BtHso0ECuTKeq70ZhW?si=79e6006e92104e51
There might be a better way to do this than using .split but i'm not sure?
EDIT: Adding extra code here that is used for other functions such as getting the array of user inputs, incase it is useful.
const getAllMusic = async () => {
try {
const { ethereum } = window;
if (ethereum) {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const musicPortalContract = new ethers.Contract(contractAddress, contractABI, signer);
const music = await musicPortalContract.getAllMusic();
console.log("lets surf")
let musicArray = [];
musics.forEach(music => {
musicArray.push({
address: music.owner,
message: music.message
});
});
/* Store our data in React State*/
setAllMusic(musicArray);
} else {
console.log("Ethereum object doesn't exist!")
}
} catch (error) {
console.log(error);
}
}
and the related html to it
{allMusics.map((wave, index) => {
return (
<div key={index}>
<div><iframe src={music.message} width="300" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe></div>

Is it possible to populate the input bar in webchat with an onclick method

I'm attempting to display a list of popular questions to the user, when they click them I want them to populate the input bar and/or send the message to the bot via the directline connection.
I've attempted using the ReactDOM.getRootNode() and tracking down the input node and setting the .value attribute, but this does not populate the field. I assume there is some sort of form validation that prevents this.
Also, if I console log the input node then save it as a global variable in the console screen I can change the value that way, but then the message will not actually be able to be sent, hitting enter or the send arrow does nothing. While it may seem that the suggestedActions option would work well for this particular application, I CANNOT use it for this use case.
const [chosenOption, setChosenOption] = useState(null);
const getRootNode = (componentRoot) =>{
let root = ReactDom.findDOMNode(componentRoot)
let inputBar = root.lastChild.lastChild.firstChild.firstChild
console.log('Initial Console log ',inputBar)
setInputBar(inputBar)
}
//in render method
{(inputBar && chosenOption) && (inputBar.value = chosenOption)}
this is the function I tried to use to find the node, the chosen option works as intended, but I cannot change the value in a usable way.
I would like the user to click on a <p> element which changes the chosenOption value and for that choice to populate the input bar and/or send a that message to the bot over directline connection.What I'm trying to accomplish
You can use Web Chat's store to dispatch events to set the send box (WEB_CHAT/SET_SEND_BOX) or send a message (WEB_CHAT/SEND_MESSAGE) when an item gets clicked. Take a look at the code snippet below.
Simple HTML
<body>
<div class="container">
<div class="details">
<p>Hello World!</p>
<p>My name is TJ</p>
<p>I am from Denver</p>
</div>
<div class="wrapper">
<div id="webchat" class="webchat" role="main"></div>
</div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
// Initialize Web Chat store
const store = window.WebChat.createStore();
// Get all paragraph elements and add on click listener
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
paragraph.addEventListener('click', ({ target: { textContent: text }}) => {
// Dispatch set send box event
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
});
}
(async function () {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
React Version
import React, { useState, useEffect } from 'react';
import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
const WebChat = props => {
const [directLine, setDirectLine] = useState();
useEffect(() => {
const initializeDirectLine = async () => {
const res = await fetch('http://localhost:3978/directline/token', { method: 'POST' });
const { token } = await res.json();
setDirectLine(createDirectLine({ token }));
};
initializeDirectLine();
}, []);
return directLine
? <ReactWebChat directLine={directLine} {...props} />
: "Connecting..."
}
export default () => {
const [store] = useState(createStore());
const items = ["Hello World!", "My name is TJ.", "I am from Denver."]
const click = ({target: { textContent: text }}) => {
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
}
return (
<div>
<div>
{ items.map((item, index) => <p key={index} onClick={click}>{ item }</p>) }
</div>
<WebChat store={store} />
</div>
)
};
Screenshot
For more details, take a look at the Programmatic Post as Activity Web Chat sample.
Hope this helps!

error in uploading the image from angular to nodejs

My Html file is -
<form method="post" [formGroup]="orderForm" enctype="multipart/form-data" (ngSubmit)="OnSubmit(orderForm.value)" >
<div class="form-group">
<label for="image">Select Branch Image</label>
<input type="file" formControlName="branchImg" (change)="onFileChange($event)" class="form-control-file" id="image">
</div>
</form>
and my .ts file is -
public orderForm: FormGroup;
onFileChange(event) {
const reader = new FileReader();
if (event.target.files && event.target.files.length) {
const [file] = event.target.files;
reader.readAsDataURL(file);
reader.onload = () => {
this.orderForm.patchValue({
branchImg: reader.result
});
};
}
}
ngOnInit() {
this.orderForm = this.formBuilder.group({
branchImg: [null, Validators.required]
});
}
and then submit the form.
I am supposed to get the image address and the upload that address in cloudinary
But when I am consoling the body in my nodejs app
it gives something like this-
branchImg: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/7QCEUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAGccAigAYkZCTUQwMTAwMGE4MjBkMDAwMD and so on.
I don't think that it is the images address. Can anyone tell me that what is this? and how to get that image's address which I will upload to cloudinary
As the Eric suggest -
my app.js code is
router.post('/branch',(req,res) =>{
const body = req.body;
const base64Data = body.branchImg.replace(/^data:image\/png;base64,/, "");
console.log(base64Data);
fs.writeFile("out.jpg", base64Data, 'base64', function(err,result) {
console.log(result);
});
});
it gives result as undefined
That basically is a base64 encoding of the image data. What you need to do after you get that is write that to a file, and then upload it to cloudinary
//this will write the base64 data as a jpg file to your local disk
require("fs").writeFile("out.jpg", base64Data, 'base64', function(err) {
//after you write it to disk, use the callback space here to upload said file
//to your cloudinary endpoint
});

How can I convert from user input image to URL in react

I'm taking an image as an input from user using the below code
<input type="file" onChange={this.fileUpload}/>
I want to convert the image into URL.
This is what I used in fileUpload() function
fileUpload = (event) => {
let src = event.target.value.getAsDataURL();
this.setState({
image: src
});
}
Please let me know how to convert image into URL.
You can use the function below as ImageChange function and use the state variable imagePreviewUrl to preview the image.
_handleImageChange(e) {
let reader = new FileReader();
let file = e.target.files[0];
reader.onloadend = () => {
this.setState({
file: file,
imagePreviewUrl: reader.result
});
}
reader.readAsDataURL(file)
}
Add ref to your input tag, so you can access the dom for that element:
<input type="file" ref={this.myFiles} onChange={this.fileUpload}/>
fileUpload=()=>{
// Now get files in the FileList object
const files = this.myFiles.files
// Define what type of file to accept:
const accept = ["image/png"];
if (accept.indexOf(files[0].mediaType) > -1) {
this.setState({
image: files[0].getAsDataURL()
})
}
}
More infos: https://developer.mozilla.org/en-US/docs/Web/API/File/getAsDataURL

How to use cheerio to get the URL of an image on a given page for ALL cases

right now I have a function that looks like this:
static getPageImg(url) {
return new Promise((resolve, reject) => {
//get our html
axios.get(url)
.then(resp => {
//html
const html = resp.data;
//load into a $
const $ = cheerio.load(html);
//find ourself a img
const src = url + "/" + $("body").find("img")[0].attribs.src;
//make sure there are no extra slashes
resolve(src.replace(/([^:]\/)\/+/g, "$1"));
})
.catch(err => {
reject(err);
});
});
}
this will handle the average case where the page uses a relative path to link to an image, and the host name is the same as the URL provided.
However,
most of the time the URL scheme will be more complex, like for example the URL might be stackoverflow.com/something/asdasd and what I need is to get stackoverflow.com/someimage link. Or the more interesting case where a CDN is used and the images come from a separate server. For example if I want to link to something from imgur ill give a link like : http://imgur.com/gallery/epqDj. But the actual location of the image is at http://i.imgur.com/pK0thAm.jpg a subdomain of the website. More interesting is the fact that if i was to get the src attribute I would have: "//i.imgur.com/pK0thAm.jpg".
Now I imagine there must be a simple way to get this image, as the browser can very quickly and easily do a "open window in new tab" so I am wondering if anyone knows an easy way to do this other than writing a big function that can handle all these cases.
Thank you!
This is my function that ended up working for all my test cases uysing nodes built in URL type. I had to just use the resolve function.
static getPageImg(url) {
return new Promise((resolve, reject) => {
//get our html
axios.get(url)
.then(resp => {
//html
const html = resp.data;
//load into a $
const $ = cheerio.load(html);
//find ourself a img
const retURL = nodeURL.resolve(url,$("body").find("img")[0].attribs.src);
resolve(retURL);
})
.catch(err => {
reject(err);
});
});
}