if i have an Reactjs input text element with onPaste event assigned to it, how could I get the pasted value in the response?
at the moment what i get in the console is a SyntheticClipboardEvent with all properties as null. I read that the console.log is a async checker so thats why the majority of values are null as they are looking ahead.
However I am wondering how to get the value.
Cheers
onPaste: function(e) {
console.log(e.clipboardData.getData('Text'));
},
https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
The formats are Unicode strings giving the type or format of the data,
generally given by a MIME type. Some values that are not MIME types
are special-cased for legacy reasons (for example "text").
example:
onPaste: function(e) {
console.log(e.clipboardData.getData('Text'));
console.log(e.clipboardData.getData('text/plain'));
console.log(e.clipboardData.getData('text/html'));
console.log(e.clipboardData.getData('text/rtf'));
console.log(e.clipboardData.getData('Url'));
console.log(e.clipboardData.getData('text/uri-list'));
console.log(e.clipboardData.getData('text/x-moz-url'));
}
Here is maybe a simpler "no paste" example using React hooks
export default function NoPasteExample() {
const classes = useStyles();
const [val, setVal] = React.useState("");
const [pasted, setPasted] = React.useState(false);
const handleChange = e => {
if (!pasted) {
setVal(e.target.value);
}
setPasted(false);
};
const handlePaste = () => {
setPasted(true);
};
return (
<form className={classes.root} noValidate autoComplete="off">
<div>
<TextField
value={val}
onPaste={handlePaste}
onChange={e => handleChange(e)}
/>
</div>
</form>
);
}
live demo: https://codesandbox.io/s/material-demo-w61eo?fontsize=14&hidenavigation=1&theme=dark
the data can be found on clipboardData, and parsed to string as follows:
event.clipboardData.items[0].getAsString(text=>{
// do something
})
For me this is quick and worked.
onPaste event fires before the input's value is changed.
So we need to use e.persist()
<input
onPaste={(e)=>{
e.persist();
setTimeout(()=>{ this.handleChange(e)},4)}
}
value={this.state.value}/>
first include Facebook DataTransfer module:
var DataTransfer = require('fbjs/lib/DataTransfer');
then you can do:
onPaste: function (evt) {
var data = new DataTransfer(evt.clipboardData);
var text = data.getText();
var html = data.getHTML();
var files = data.getFiles();
},
you welcome ;)
Related
I'm trying to make a page that gets picture from a server and once all pictures are downloaded display them, but for some reason the page doesn't re-render when I update the state.
I've seen the other answers to this question that you have to pass a fresh array to the setImages function and not an updated version of the previous array, I'm doing that but it still doesn't work.
(the interesting thing is that if I put a console.log in an useEffect it does log the text when the array is re-rendered, but the page does not show the updated information)
If anyone can help out would be greatly appreciated!
Here is my code.
export function Profile() {
const user = JSON.parse(window.localStorage.getItem("user"));
const [imgs, setImages] = useState([]);
const [num, setNum] = useState(0);
const [finish, setFinish] = useState(false);
const getImages = async () => {
if (finish) return;
let imgarr = [];
let temp = num;
let filename = "";
let local = false;
while(temp < num+30) {
fetch("/get-my-images?id=" + user.id + "&logged=" + user.loggonToken + "&num=" + temp)
.then(response => {
if(response.status !== 200) {
setFinish(true);
temp = num+30;
local = true;
}
filename = response.headers.get("File-Name");
return response.blob()
})
.then(function(imageBlob) {
if(local) return;
const imageObjectURL = URL.createObjectURL(imageBlob);
imgarr[temp - num] = <img name={filename} alt="shot" className="img" src={imageObjectURL} key={temp} />
temp++;
});
}
setNum(temp)
setImages(prev => [...prev, ...imgarr]);
}
async function handleClick() {
await getImages();
}
return (
<div>
<div className="img-container">
{imgs.map(i => {
return (
i.props.name && <div className="img-card">
<div className="img-tag-container" onClick={(e) => handleView(i.props.name)}>{i}</div>
<div className="img-info">
<h3 className="title" onClick={() => handleView(i.props.name)}>{i.props.name.substr(i.props.name.lastIndexOf("\\")+1)}<span>{i.props.isFlagged ? "Flagged" : ""}</span></h3>
</div>
</div>
)
})}
</div>
<div className="btn-container"><button className="load-btn" disabled={finish} onClick={handleClick}>{imgs.length === 0 ? "Load Images" : "Load More"}</button></div>
</div>
)
}
I think your method of creating the new array is correct. You are passing an updater callback to the useState() updater function which returns a concatenation of the previous images and the new images, which should return a fresh array.
When using collection-based state variables, I highly recommend setting the key property of rendered children. Have you tried assigning a unique key to <div className="img-card">?. It appears that i.props.name is unique enough to work as a key.
Keys are how React associates individual items in a collection to their corresponding rendered DOM elements. They are especially important if you modify that collection. Whenever there's an issue with rendering collections, I always make sure the keys are valid and unique. Even if adding a key doesn't fix your issue, I would still highly recommend keeping it for performance reasons.
It is related to Array characteristics of javascript.
And the reason of the console log is related with console log print moment.
So it should be shown later updated for you.
There are several approaches.
const getImages = async () => {
... ...
setNum(temp)
const newImage = [...prev, ...imgarr];
setImages(prev => newImage);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => JOSN.parse(JSON.object([...prev, ...imgarr]);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => [...prev, ...imgarr].slice(0));
}
Maybe it could work.
Hope it will be helpful for you.
Ok the problem for me was the server was not sending a proper filename header so it was always null so the condition i.props.name was never true... lol sorry for the confusion.
So the moral of this story is, always make sure that it's not something else in your code that causes the bad behavior before starting to look for other solutions...
First, please check out my code.
There might be some misspell! ( I rewrote my code )
const test = () => {
const [files, setFiles] = useState([]);
const handleFile = (e) => {
for(let i=0; i<e.target.files.length; i++){
setFiles([...files, e.target.files[i]
}
}
return (
{
files.map((file, index) => (
<div key={index}>
<p> {file[index].name} </p>
<button> Delete </button>
</div>
))
}
<label onChange={handleFile}>
<input type='file' mutiple /> Attach File
</label>
)
}
When I render with this code, makes errors,
TypeError: Cannot read Properties of undefined (reading 'name')
{file[index].name}
like this.
When I delete .name, only buttons are being rendered. ( as I didn't specify what to render of file's property. )
Moreover, I'm trying to render multiple files at once. As I set my input type as multiple, I can select multiple files when I choose to upload things.
However, even though I selected two or three, It only renders just one.
I hope my explanation describes my situation well. If you have any questions, please ask me!
I'm looking forward to hearing from you!
If you update the same state multiple time in the same handler function only the last call will work for performance issue. You have to change your onChange handler to something like:
const handleFile = (e) => {
const newFiles = []
for(let i = 0; i < e.target.files.length; i++){
newFiles.push(e.target.files[i])
}
setFiles(newFiles)
}
also as mentioned in another answer, change the "name" line to this:
<p>{file.name}</p>
For anyone who has the same trouble as me, Here is a stopgap.
const [files, setFiles] = useState([]);
const handleFile = (e) => {
setFiles([...files, e.target.files[0], e.target.files[1], e.target.files[2]])
if(e.target.files[1] == null) {
setFiles([...files, e.target.files[0]])
} if (e.target.files[1] && e.target.files[2] == null) {
setFiles([...files, e.target.files[0], e.target.files[1]])
}
};
Using conditional statement, you can control the index of files. However, I still don't know what is the other way to deal with the problem.
Anyway, I hope my answer helps you some!
You dont need the [index] part inside the map so should look like this
<p>{file.name}</p>
Should work now :)
UPDATE FOR MULTIPLE UPLOADS
const handleFile = (e) => {
const newSelectedArray = files;
newSelectedArray.push({
...e.target.files[0],
identifier: e.target.filename //check this please i cant remember what the array name is called for filename. You dont need this yet but once we get the first bit working we can include it in something cool.
});
setFiles(newSelectedArray)
}
Let me know on this one as it was a nightmare for me too so hopefully that will work
I am not sure if i am missing out something, but I think looping like this is redundant when instead you can simply do
const handleFile = (e) => {
setFiles(e.target.files)
}
Also, when you want to access the previous state value you should probably access the previous state value by using a callback function inside setFiles like this
for(let i=0; i<e.target.files.length; i++){
setFiles((prevfiles)=>[...prevFiles,e.target.files[i]])
}
EDIT:
I am also mentioning a fix not included in the original answer since it had already been stated by #Matt at the time of posting.I am editing this answer only for the sake of providing the complete answer
file[index].name had to be changed to file.name
In my react electron app, that it is working with an API, I receive JSON values to display data into the components. So for example I have a Features component:
const Features = () => {
const { title } = useSelector(({ titles }) => titles);
let string = title.features;
// the string can contain some html tags. Example bellow:
// sting = 'This is a string containing a href to Google';
string = string.replace(/href="(.*?)"/g, function() {
return `onClick="${() => shell.openExternal('www.google.com')}"`;
});
return (
<>
<Heading>Features</Heading>
<Text content={parsedHTML} />
</>
);
};
What I want is to replace the href attribute with onClick and assign Electron's shell.openExternal() function.
The string.replace() callback function does that, but when I click on the <a> element, the app throws next error:
error: uncaughtException: Expected onClick listener to be a
function, instead got a value of string type.
UPDATE
Also tried this logic and the same error occurs:
global.openInBrowser = openInBrowser; // openInBrowser is basically a function that calls shell.openExternal(url)
const re = new RegExp('<a([^>]* )href="([^"]+)"', 'g');
string = string.replace(re, '<a$1href="#" onClick="openInBrowser(\'$2\')"');
Here's a link to Sandbox rep.
How do I do this correctly?
The onclick not being set on the React element is actually expected behavior.
Because there's an XSS security risk when evaling the onclick string. The recommended solution is to use the replace option in html-react-parser.
you can also use dangerouslySetInnerHTML which involves security risk.
Sandbox Demo
export default function App() {
let string =
'This is a string containing html link to Google';
const re = new RegExp('<a([^>]* )href="([^"]+)"', "g");
let replaced = string.replace(re, "<a onclick=\"alert('$2')\"");
return (
<div className="App">
<p dangerouslySetInnerHTML={{__html: replaced}}></p>
<p>{parse(string, {
replace: domNode => {
if (domNode.name === 'a') {
let href = domNode.attribs.href
domNode.attribs.onClick = () => { alert(href) }
delete domNode.attribs.href
}
}
})}</p>
</div>
);
}
Not sure on specifics of electron, but passing a (function(){functionName()})() would not work in html if there is no functionName variable available on window scope. Being there is a global environment in electron this might answer your question:
const Features = () => {
const { title } = useSelector(({ titles }) => titles);
let string = title.features;
// the string can contain some html tags. Example bellow:
// sting = 'This is a string containing a href to Google';
function runOpen(href){
shell.openExternal(href)
}
global.runOpen = runOpen;
string = string.replace(/href="(.*?)"/g, function() {
return `onClick="runOpen(${'www.google.com'})"`;
});
return (
<>
<Heading>Features</Heading>
<Text content={parsedHTML} />
</>
);
};
if it doesnt you can use something like onclick="console.log(this)" to find out what is the scope the onclick runs in and futher assign your runOpen variable there.
Following is my code, which was working fine till I added changePagesize method and trying to console e event. Site stops responding in Firefox. Let me know what I am doing wrong and why message is not consoling.
Full Code - CodeSandBox
Code -
const changePagesize = e => {
e.persist();
console.log(`Element`, e);
setPagesize(e.target.value);
};
I don't know if you need it but just remove e.persist() and it will work as expected.
const changePagesize = e => {
const { preventDefault, stopPropagation } = e;
const { name, value } = e.target;
console.log(`prevent`, preventDefault);
console.log(`stop`, stopPropagation);
console.log(`name`, name);
console.log(`value`, value);
// e.persist();
setPagesize(e.target.value);
};
You can destructure it like this if you know what values you need from (e).
I am using lit-html and I can't seem to get the renderer to update, or any function to run on user input. I would like to check an input field on typing. I can't find any examples online showing responding to user input.
Here is the full code.
my_input.ts
import {html, directive, Part} from 'lit-html';
const stateMap = new WeakMap();
export const stateManager = directive(() => (part: Part) => {
let mystate: IInputData = stateMap.get(part);
if(mystate === undefined)
{
mystate = {
auto_id: Math.floor(Math.random() * 100000 + 1),
helper_eval: "",
input_message: "",
tab_index: 0,
input_value: "",
span_message: "",
}
stateMap.set(part, mystate);
}
else {
console.log("Hey");
part.setValue(mystate);
part.commit();
}
});
export interface IInputData {
auto_id: number,
helper_eval: string,
input_message: string,
tab_index: number,
input_value: string,
span_message: string
}
export let my_input = () => {
let d: IInputData = stateManager() as unknown as IInputData;
return html`
<section>
<label for="${d.auto_id}"
title="${d.helper_eval}">
${d.input_message} ⓘ
<span class="float_left">
${d.span_message}
</span>
</label>
<input id="${d.auto_id}"
tabindex="${d.tab_index}" value="${d.input_value}">
</input>
<section>
`;
}
app.ts
const myTemplate = () => html`
<div>
${renderCounter(0)}
</div>`;
const renderCounter = directive((initialValue) => (part: any) => {
if(part.value === undefined)
{
part.setValue(initialValue);
}
else { part.setValue(part.value +1)}
}
);
export let app = (data: TemplateResult[]) => html`
${data}
`;
render(app([my_input(), myTemplate]), document.body);
I have included the render counting example, and don't see the render counter increase while typing in the input field. I am also not sure how I can add hooks to respond to the IInputData that change in that template. My best guess is to add some sort of callback function to the stateManager directive.
If you are looking for a basic usage of lit-html, please read on. If you are trying to figure out how to implement a custom directive, then I hope someone else can answer it for you. Also, I don't know Typescript so my answer below is in a plain JavaScript.
The idea behind lit-html is for you to call render() function every time you want to update the HTML. It does not self-update its output.
So, to use it with an input element, you need to implement a callback function to update template's dependent data-structure and to re-render. The callback must be attached to your template with #input/#change/#click etc.
Here is a simple example (loosely based on yours):
// create a data object containing the template variables
const data = {
span_message: "input text copy appears here"
}
// callback function for the input event
const inputCallback = (evt) => {
// update the template data
d.span_message = evt.target.value;
// re-render the template
render(myTemplate(data), document.body);
};
// function to generate a template
// d - data object, which controls the template
const myTemplate = (d) => html`
<input
#input=inputCallback
</input>
<span>
${d.span_message}
</span>`;
// initial rendering of the template
render(myTemplate(data), document.body);
Hope this helps