I'm trying to get some code working which will highlight links automatically in an rich text editor. This is not my own written code this is from draft js and is provided by many different web pages because this is the absolutlly basic how it should work out.
My major problem is that when I deifinine the linkDecorator of the type CompositeDecorator the compiler tells me that the linkDecorator is $ReadOnlyArray which can't handle the provided input. I have absolutly no idea what I'm doing wrong because for people on about 10 different websites exactly this code works out well. Even in an online compiler were you can compile the code yourself it works.
Also practically the programm won't throw errors but it simply won't do anything anymore after this line.
Thanks in advance for any help!!!
function findLinkEntities(contentBlock, callback, contentState){
contentBlock.findEntityRanges((character) => {
const entityKey = character.getEntity();
return(
entityKey !== null &&
contentState.getEntity(entityKey).getElementById() === 'LINK'
)
}, callback);
}
const Link = (props) => {
const { url,linkText} = props.contentState
.getEntity(props.entityKey)
.getData()
return (
<a style={{color: '#006cb7', textDecoration: 'underline'}}
href={url}>
{linkText || props.children}
</a>
)
}
const linkDecorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
}
])
Related
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
I am facing difficulties rendering array on screen when I navigate back to the component. I have been trying and searching since morning for the solution but no luck. Please let me explain
I have a react component called ShowTags.tsx that contains a callback function handleTagReading() which return strings every second (async behavior).
When the string tag arrives I am storing it in [storeTags] state array and in the return method, using map function to iterate and display the tags.
The Sample code
export default function ShowTags() {
//array to store string tags
const [storeTags, setStoreTags] = useState<String[]>([]);
//callback method
const handleTagReading = (tag) => {
console.log("print the tag" + tag) // this line runs everytime
setStoreTags(storeTags => [...storeTags!, tag]);
}
return (
<>
/*This component contains a button, on button click it runs the loop method which runs
the parent callback method*/
<ReadTags parentCallback = {handleTagReading} />
<div className="container">
{storeTags && storeTags!.map((item, index) => {
return (
<div>
{item}
</div>)
})}
</div>
</>
)
}
This code works perfectly as long as I am on The ShowTags component for the first time. The moment I navigate to a different component and come back, the map method shows nothing. But console.log() still runs showing the tags coming from a callback.
I have tried using the useEffect(), cleanup(), boolean states variables, non state variables but the component does not render when switching back to ShowTags component.
Please help me out here and let me know if you need more information
UPDATE -edit
For simplicity I said async behavior but actually I am using Serial USB API to read data from RFID reader (external hardware device connected via USB)
The ReadTags() component contains lot of code but I am sharing the necessary bits
export default function ReadTags(props) {
//send diffenent commands to reader on button press
async function sendSerialLine() {
try{
await writer.write(dataToSend);
}catch(e){
console.log("the write error")
setShowConnect(true)
}
}
//The listenToPort method runs continuously when the RFID reader gets connected via Serial USB and does not stop.
async function listenToPort(){
/*serial USB API implementation*/
textDecoder = new TextDecoderStream();
readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
//parent callback
props.parentCallback(value);
}}
return (
<div>
<p onClick={() => sendSerialLine()} className="btn btn-primary">Start Reading</p>
</div>
)
}
Try to use useCallback with handleTagReading
I'm currently working on a project which involves using multiple wysiwyg editors. I have previously used react-draft in the same project but has always been used with static elements eg, each editor is fixed.
In my case, my editors are created on the fly, (min 1, max 15) editors. I'm rendering these into my containers using map() with constructed object each time. Allowing the user to click + or - buttons to create / remove a editor.
for example to create a new editor into, i push to then map over the components array which looks something like the below:
components: [
{
id:1,
type: 'default',
contentValue: [
title: 'content-block',
value: null,
editorState: EditorState.CreateEmpty(),
]
}
]
I am able to render multiple editors just fine and createEmpty ediorstates. My issue is when i try to update the contents editor state.
Usually to update a single editor id use:
onEditorStateChange = editorState => {
this.setState({
editorstate,
})
}
However, given the fact my editors are dynamically rendered, i have the editor state isolated within the "Components" array. So i've tried the following which did not work:
In Render
this.state.components.map((obj) => {
return (
<Editor
editorState={obj.contentValue.editorState}
onEditorStateChange={(e) => this.onEditorStateChange(e, obj.id)}
/>
);
}
onEditorStateChange
onEditorStateChange(e, id){
const { components } = this.state;
const x = { components };
for (const i in x){
if(x[i].id ==== id){
x[i].contentValue.editorState = e;
}
}
this.setState({components: x})
}
Upon debugging, the "setState" does get called in the above snippet, and it does enter the if statement, but no values are set in my editorState.
I'm happy for alternative ways to be suggested, as long as this will work with dynamically rendered components.
I require this value to be set, as i will be converting to HTML / string and using the content to save to a database.
I hope i have explained this well, if not please let me know and ill be happy to provide further information / snippets.
Thank you in advance.
Okay, i figured out a solution.
inside my onEditorStateChange() i update the value with the parameter (e) which was initally passed in. Using draftToHtml i convert it to raw and pass it to set state as shown below:
onEditorStateChange(e, id) {
const { components } = this.state;
console.log(e);
const x = components;
for (const i in x) {
if (x[i].id === id) {
x[i].contentValue.editorState = e;
x[i].value = draftToHtml(convertToRaw(e.getCurrentContent()));
//console.log(x[i]);
}
}
this.setState({
components: x,
});
}
This gives me the a HTML value which i can now convert to string and save to the database.
Hope this helps someone else with the same issue :)
I am trying to use the sweetalert-react package (https://github.com/chentsulin/sweetalert-react) as a modal for my app.
Right now I got it to work, but I want to be able to display a const that has my component:
const clusterDoors = lock.doors.map(clusterDoor => {
return (
<div key={clusterDoor.port_id}>
<ClusterListItem
clusterDoor={clusterDoor}
customer={
clusterDoor.allocation.customer ? (
keyedCustomers[clusterDoor.allocation.customer]
) : (
false
)
}
.....
So I read their docs and found out that I can achieve that with ReactDOMServer.renderToStaticMarkup. So I simple need to:
text={renderToStaticMarkup(<MyComponent />)}
But the problem is that my component is inside of a constant, so if I try to do:
text={renderToStaticMarkup({clusterDoors})}
I will get the error:
You must pass a valid ReactElement.
I wonder if there's some workaround to this?
I did some research and tried also tried to do:
const clusterDoors = React.createClass({
render: function() {
lock.doors.map(clusterDoor => {
return (
<div key={clusterDoor.port_id}>
<ClusterListItem
clusterDoor={clusterDoor}
customer={
clusterDoor.allocation.customer ? (
keyedCustomers[clusterDoor.allocation.customer]
) : (
false
)
}
delivery={
clusterDoor.allocation.delivery ? (
keyedDeliveries[clusterDoor.allocation.delivery]
) : (
false
)
}
/>
</div>
)
})
}
})
But that didn't do the trick.
If I pass it a valid react component (ClusterListItem) my app won't break, but nothing will show because of the array clusterDoor wont exist.
I hope I explained my situation well. Thanks for reading.
The problem with your code is that you are passing an array of elements because clusterDoors is an array and renderToStaticMarkup is expecting a single element. Therefore you are getting this error.
Solution: Just wrap your array in a div tag so it becomes a single node element like this
text={renderToStaticMarkup(<div>{clusterDoors}</div>)}
So I just started trying to learn rxjs and decided that I would implement it on a UI that I'm currently working on with React (I have time to do so, so I went for it). However, I'm still having a hard time wrapping my head around how it actually works... Not only "basic" stuff like when to actually use a Subject and when to use an Observable, or when to just use React's local state instead, but also how to chain methods and so on. That's all too broad though, so here's the specific problem I have.
Say I have a UI where there's a list of filters (buttons) that are all clickeable. Any time I click on one of them I want to, first of all, make sure that the actions that follow will debounce (as to avoid making network requests too soon and too often), then I want to make sure that if it's clicked (active), it will get pushed into an array and if it gets clicked again, it will leave the array. Now, this array should ultimately include all of the buttons (filters) that are currently clicked or selected.
Then, when the debounce time is done, I want to be able to use that array and send it via Ajax to my server and do some stuff with it.
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.debounce(1000)
// .do(x => this.setState({
// arr: this.state.arr.push(x)
// }))
.subscribe(
click => this.search(click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
I could easily go about this without RxJS and just check the array myself and use a small debounce and what not, but I chose to go this way because I actually want to try to understand it and then be able to use it on bigger scenarios. However, I must admit I'm way lost about the best approach. There are so many methods and different things involved with this (both the pattern and the library) and I'm just kind of stuck here.
Anyways, any and all help (as well as general comments about how to improve this code) are welcome. Thanks in advance!
---------------------------------UPDATE---------------------------------
I have implemented a part of Mark's suggestion into my code, but this still presents two problems:
1- I'm still not sure as to how to filter the results so that the array will only hold IDs for the buttons that are clicked (and active). So, in other words, these would be the actions:
Click a button once -> have its ID go into array
Click same button again (it could be immediately after the first
click or at any other time) -> remove its ID from array.
This has to work in order to actually send the array with the correct filters via ajax. Now, I'm not even sure that this is a possible operation with RxJS, but one can dream... (Also, I'm willing to bet that it is).
2- Perhaps this is an even bigger issue: how can I actually maintain this array while I'm on this view. I'm guessing I could use React's local state for this, just don't know how to do it with RxJS. Because as it currently is, the buffer returns only the button/s that has/have been clicked before the debounce time is over, which means that it "creates" a new array each time. This is clearly not the right behavior. It should always point to an existing array and filter and work with it.
Here's the current code:
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.buffer(this.click.debounce(2000))
.subscribe(
click => console.log('click', click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
Thanks, all, again!
Make your filter items an Observable streams of click events using Rx.Observable.fromevent (see https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/events.md#converting-a-dom-event-to-a-rxjs-observable-sequence) - it understands a multi-element selector for the click handling.
You want to keep receiving click events until a debounce has been hit (user has enabled/disabled all filters she wants to use). You can use the Buffer operator for this with a closingSelector which needs to emit a value when to close the buffer and emit the buffered values.
But leaves the issue how to know the current actual state.
UPDATE
It seems to be far easier to use the .scan operator to create your filterState array and debounce these.
const sources = document.querySelectorAll('input[type=checkbox]');
const clicksStream = Rx.Observable.fromEvent(sources, 'click')
.map(evt => ({
name: evt.target.name,
enabled: evt.target.checked
}));
const filterStatesStream = clicksStream.scan((acc, curr) => {
acc[curr.name] = curr.enabled;
return acc
}, {})
.debounce(5 * 1000)
filterStatesStream.subscribe(currentFilterState => console.log('time to do something with the current filter state: ', currentFilterState);
(https://jsfiddle.net/crunchie84/n1x06016/6/)
Actually, your problem is about RxJS, not React itself. So it is easy. Suppose you have two function:
const removeTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags
else
return tags.splice(index, 1, 0)
}
const addTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags.push(tagName)
else
return tags
}
Then you can either using scan:
const modifyTags$ = new Subject()
modifyTags$.pipe(
scan((tags, action) => action(tags), [])
).subscribe(tags => sendRequest(tags))
modifyTags$.next(addTag('a'))
modifyTags$.next(addTag('b'))
modifyTags$.next(removeTag('a'))
Or having a separate object for tags:
const tags$ = new BehaviorSubject([])
const modifyTags$ = new Subject()
tags$.pipe(
switchMap(
tags => modifyTags$.pipe(
map(action => action(tags))
)
)
).subscribe(tags$)
tags$.subscribe(tags => sendRequest(tags))