How to access onCopy event value in React - reactjs

I want to edit copied content in onCopy event. But I checked that the parameter of onCopy event is a clipboardevent. So, is there a way that i can edit content on Clipboard through clipboardevent vairiable. Codes are like
<div onCopy = {(e)=>{//edit content through variable e}}>
//something here
</div>

So, if you want to see the value that you are copying, just edit the code like this.
<div onCopy = onCopy={e => console.log(e.target.innerText)}>
the text is placed inside the "innerText" key.
For React just initialize a state using useState, where you can save the value.
const Test = () => {
const [data, setData] = useState(null)
return (
<div onCopy={e => setData(e.target.innerText)}>
Hello
</div>
)
}

For a single <div> with no child elements, you can access the copied content using:
e.target.innerText
OR
e.target.textContent
Such as:
<div onCopy={(e) => console.log(e.target.innerText)}></div>
// You can use the value like this:
function MyComponent() {
const [copiedValue, setCopiedValue] = useState("");
return (
<>
<div>{copiedValue}</div>
<div onCopy={(e) => setCopiedValue(e.target.innerText)}></div>
</>
)
}
NOTE: There are important differences between innerText and textContent if your <div> has any child nodes. One big difference is:
textContent gets the content of all elements, including <script> and <style> elements. In contrast, innerText only shows “human-readable” elements.
Source MDN Doc
I'm not getting the value of what I selected, I'm getting the entire value of the element!
Using the above event target properties, you will only be able to access the entire contents of the element being copied. If you want to copy the selected text from the user, you could do something like this:
function MyComponent() {
const [copiedValue, setCopiedValue] = useState("");
const handleOnCopy = (e) => {
const selectedText = document.getSelection(); // <-- this is different
setCopiedValue(selectedText);
};
return (
<>
<div>{copiedValue}</div>
<div onCopy={(e) => handleOnCopy(e)}></div>
</>
)
}
I'm getting undefined when accessing e.target.innerText and e.target.textContent!
This could be because you're trying to access the value of an <input> element. If you're trying to access a checkbox's boolean state use e.target.checked. For all the other <input>'s, use e.target.value

Related

React - Result List rerenders if search text is entered

I'm still unexperienced with react so that even after searching for a solution and finding some pointers I still cant grasp what the problem is or how to solve it.
I have a Component that renders a list of images. It also contains a search input. I copy the search input onChanged to the state. If onKeyPressed is the return key or when the search button is pressed, that text is again copied from state to the state.searchTerm. The search itself is an effect that watches for changes in searchTerm an then executes a search, updating the list of images. However I feel like, because I change the state with every onChange in the search input, I trigger a re-render of the entire component including the list of images which is just annoying. How can I get rid of this?
I tried to shrink my styled and dynamic code to a minimal working version. What would be the best way to solve this? Would it help to split list and search into separate components with individual state with the parent just holding the list of assets, passing it to the list child and the search child getting a reference to onSearch?
But then I just move the problem since if the search child re-renders, the parent will as well, right?
function AssetListTool ({}) {
const [assets, setAssets] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [searchText, setSearchText] = useState('');
const params = {
limit: 30,
sort: 'title:desc',
searchTerm
};
const Asset = memo(function ({asset }) {
return <>
<div className="asset">
<img src={asset.thumbnail} />
</div>
</>;
});
useEffect(() => {
const matchingAssets = assetApi.getAllAssets({...params });
Promise.all([matchingAssets],
).then(responses => {
setAssets(assets.concat(responses[0].items));
});
}, [searchTerm]);
const onSearchTextChanged = useCallback((event) => {
setSearchText(event.target.value);
}, [searchText]);
function onSearchKeyPressed(event) {
if (event.key === 'Enter') {
onSearch();
}
}
function onSearch() {
setAssets([]);
setSearchTerm(searchText);
}
return (
<>
<div>
<div>
<input onChange={onSearchTextChanged} onKeyPress={onSearchKeyPressed} value={searchText}
type="text"/>
</div>
<div>
<button onClick={onSearch}>
<Icon icon={faSearch}/>
</button>
</div>
</div>
<div>
{assets && assets.length > 0 &&
<div>
{assets.map((asset) => <Asset asset={asset}/>)}
</div>
}
</div>
</>
);
}
export default AssetListTool;
Ah, should've searched just a little more and I was on the right track... The solution is to not touch the state for the text input and instead use a reference to it which is then read for the search as was explained here:
Getting input values without rerender
Thought about a local variable but that didnt work and using event.target.value was always missing the last input. So ref is the trick here..

React ref that depends by an element's reference does not get passed to the child components

The following code creates an object ref that's called editor, but as you see it depends by the contentDiv element that's a ref to a HTMLElement. After the editor object is created it needs to be passed to the TabularRibbon. The problem is that the editor is always null in tabular component. Even if I add a conditional contentDiv?.current, in front of this, it still remains null...
Anyone has any idea?
export const Editor = () => {
let contentDiv = useRef<HTMLDivElement>(null);
let editor = useRef<Editor>();
useEffect(() => {
let options: EditorOptions = { };
editor.current = new Editor(contentDiv.current, options);
return () => {
editor.current.dispose();
}
}, [contentDiv?.current])
return (
<div >
<TabularRibbon
editor={editor.current}
/>
<div ref={contentDiv} />
..........

React custom hook with state variable as parameter

I have a react function component which sets an array of ids based on an user event on a link click(which opens a popup with some options that can be selected and has a callback once it is closed which will return the id of the selected element). these ids are passed to a child component which has a custom hook which uses these ids to perform some action. whenever i click on the link and select an element and close the popup.. get the error
"VM10715 react_devtools_backend.js:2430 You have changed a parameter while calling a hook which is supposed to remain unchanged [Array(2)]
0: (2) ["", "asdsadsad"]
lastIndex: (...)
lastItem: (...)
length: 1"
is there a way to make this work without running into this error? please see the code sample below
const TestComp = () => {
const [newIds, setNewIds] = useState([]);
const onPopupElementSelect = (ids) => {
setNewIds([...newIds, ids]);
};
return (
//renders some components
<>
<ImageComponent images={images} ids={newIds} onClick={handleClick} />
<Popup onSelect={onPopupElementSelect} />
</>
);
};
const ImageComponent = (props) => {
const { newIds, images } = props;
const newImages = useImages(ids || ['']); //customhook that fetches image details by ids
const imgs = images.map((i) => (
<div key={i.imageId}>
<img src={i.imageUrl} alt="" />
<Link onClick={handleClick} /> //opens the popup for user to select a new
image
</div>
));
return <div>{imgs}</div>;
};
ps: the paramerter names are not the issue.. this code is just a sample to give the basic idea of what i'm trying to do.
I think it is because you gave the same name to parameter and the state may be try newID as the parameter name
const onPopupElementSelect = (newId) => {
setIds(oldIds => [...oldIds, newId]);
};

React - UseEffect not re-rendering with new data?

This is my React Hook:
function Student(props){
const [open, setOpen] = useState(false);
const [tags, setTags] = useState([]);
useEffect(()=>{
let input = document.getElementById(tagBar);
input.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById(tagButton).click();
}
});
},[tags])
const handleClick = () => {
setOpen(!open);
};
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
tagList.push(input.value);
console.log("tag");
console.log(tags);
console.log("taglist");
console.log(tagList);
setTags(tagList);
}
const tagDisplay = tags.map(t => {
return <p>{t}</p>;
})
return(
<div className="tags">
<div>
{tagDisplay}
</div>
<input type='text' id={tagBar} className="tagBar" placeholder="Add a Tag"/>
<button type="submit" id={tagButton} className="hiddenButton" onClick={addTag}></button>
<div>
);
What I am looking to do is be able to add a tag to these student elements (i have multiple but each are independent of each other) and for the added tag to show up in the tag section of my display. I also need this action to be triggerable by hitting enter on the input field.
For reasons I am not sure of, I have to put the enter binding inside useEffect (probably because the input element has not yet been rendered).
Right now when I hit enter with text in the input field, it properly updates the tags/tagList variable, seen through the console.logs however, even though I set tags to be the re-rendering condition in useEffect (and the fact that it is also 1 of my states), my page is not updating with the added tags
You are correct, the element doesn't exist on first render, which is why useEffect can be handy. As to why its not re-rendering, you are passing in tags as a dependency to check for re-render. The problem is, tags is an array, which means it compares the memory reference not the contents.
var myRay = [];
var anotherRay = myRay;
var isSame = myRay === anotherRay; // TRUE
myRay.push('new value');
var isStillSame = myRay === anotherRay; // TRUE
// setTags(sameTagListWithNewElementPushed)
// React says, no change detected, same memory reference, skip
Since your add tag method is pushing new elements into the same array reference, useEffect thinks its the same array and is not re-triggers. On top of that, React will only re-render when its props change, state changes, or a forced re-render is requested. In your case, you aren't changing state. Try this:
function addTag(){
let input = document.getElementById(tagBar);
let tagList = tags;
// Create a new array reference with the same contents
// plus the new input value added at the end
setTags([...tagList, input.value]);
}
If you don't want to use useEffect I believe you can also use useRef to get access to a node when its created. Or you can put the callback directly on the node itself with onKeyDown or onKeyPress
I can find few mistake in your code. First, you attaching event listeners by yourself which is not preferred in react. From the other side if you really need to add listener to DOM inside useEffect you should also clean after you, without that, another's listeners will be added when component re-rendered.
useEffect( () => {
const handleOnKeyDown = ( e ) => { /* code */ }
const element = document.getElementById("example")
element.addEventListener( "keydown", handleOnKeyDown )
return () => element.removeEventListener( "keydown", handleOnKeyDown ) // cleaning after effect
}, [tags])
Better way of handling events with React is by use Synthetic events and components props.
const handleOnKeyDown = event => {
/* code */
}
return (
<input onKeyDown={ handleOnKeyDown } />
)
Second thing is that each React component should have unique key. Without it, React may have trouble rendering the child list correctly and rendering all of them, which can have a bad performance impact with large lists or list items with many children. Be default this key isn't set when you use map so you should take care about this by yourself.
tags.map( (tag, index) => {
return <p key={index}>{tag}</p>;
})
Third, when you trying to add tag you again querying DOM without using react syntax. Also you updating your current state basing on previous version which can causing problems because setState is asynchronous function and sometimes can not update state immediately.
const addTag = newTag => {
setState( prevState => [ ...prevState, ...newTage ] ) // when you want to update state with previous version you should pass callback which always get correct version of state as parameter
}
I hope this review can help you with understanding React.
function Student(props) {
const [tags, setTags] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleOnKeyDown = (e) => {
if (e.keyCode === 13) {
e.preventDefault();
addTag();
}
};
function addTag() {
setTags((prev) => [...prev, inputValue]);
setInputValue("");
}
return (
<div className="tags">
<div>
{tags.map((tag, index) => (
<p key={index}>{tag}</p>
))}
</div>
<input
type="text"
onKeyDown={handleOnKeyDown}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add a Tag"
/>
<button type="submit" onClick={addTag}>
ADD
</button>
</div>
);
}

Getting props of a clicked element in React

Currently learning React and need to get a prop of a clicked element. I managed to get it, but it does not feel like the "right" react way.
This is what I have (it works):
filterProjects = (event) => {
const value = event.currentTarget.getAttribute("filterTarget")
console.log(value)
}
Initially I tried multiple things, e.g.:
const value = this.props.filterTarget or const value = event.currentTarget.this.props.filterTarget or even using ref but all returned undefined when console logging the value.
(Using this.props as it's part of a class Component.)
This is what my target element looks like:
const categories = data.allPrismicProjectCategory.edges.map((cat, index) => {
return (
<a
key={index}
onClick={this.filterProjects}
filterTarget={cat.node.uid}
>
{cat.node.data.category.text}
</a>
)
})
One simple way is passing the value itself,
onClick={() => this.filterProjects(cat.node.uid)}
And the function,
filterProjects = (value) => {
console.log(value)
}

Resources