Draft.js - CompositeDecorator: Is there a way to pass information from the strategy to the component? - javascript

Lets say my strategy calculates some numbered label. How can I pass this (e.g. via props) to the decorator component.
I know there is a props property in CompositeDecorator but how can I access it from the strategy function?

I'm a bit new to DraftJs, but based on my understanding:
Strategies should be used to identify the range of text that need to be decorated. The rendering of that decoration (which presumably includes calculating what the label should be) should be handled in the component itself, rather than the strategy.
You should be able to access the ContentState via the props object in your component, and calculate the label from that. The constructor of your component could be a good place for executing the logic for calculating the label. This also means that you might have to use a class definition for your decorator components as opposed to a pure function as shown in the examples on the draftjs website.

You can also circumvent the issue by reading the values from the text with regex. The following example is done with #draft-js-plugins:
// How the section is detected.
const strategy = (contentBlock, callback) => {
const text = contentBlock.getText();
const start = text.indexOf('<_mytag ');
const endTag = '/>';
const end = text.indexOf(endTag);
if (start !== -1 && end !== -1) {
callback(start, end + endTag.length);
}
};
// What is rendered for the detected section.
const component = ({ decoratedText }) => {
if (decoratedText) {
const label = decoratedText.match(/label="([a-zA-Z0-9/\s]*?)"/);
if (
label &&
typeof label[1] === 'string'
) {
return <div>{label[1]}</div>;
}
return null;
}
};
export const MyTagPlugin = {
decorators: [
{
strategy,
component,
},
],
};

Related

How can I pass innerHTML to an onClick function (Typescript)

I'm trying to pass the Square element's innerHTML to the onClick function. I have also tried to pass in just i but it always is equal to 100. Is there a way to either pass i when it's equal to the same value that goes into the Square or is there a way to pass the innerHTML to the function. Currently, this code generates the error:
[TS: 2532]this is possibly undefined
I'm making a grid of 100 squares, each one is a button, and each one should have it's own ID/number from 1-100 to identify them.
This is what I have currently: Grid of 100 squares arranged in 10x10 formation
export const Square = (props:any) =>{
i += 1;
if(i > 100)
{
i = 1;
}
return(
<DefaultButton styles={factionMapButton} onClick={()=>onSquareClick(this.innerHTML,props.onClick)}>{i}</DefaultButton>
);
}
const onSquareClick = (number:any,isOpen:any) => {
console.log(number);
const panelContent = document.getElementById("panelContent");
if(panelContent !== null)
{
panelContent.innerHTML = number;
}
isOpen();
}
You have quite a few problems.
You should do your best to avoid any in TypeScript, especially not liberally - that defeats the whole purpose of type-checking. If you're trying to fix type problems, you should start by also typing everything properly.
Arrow functions do not have their this altered by the calling context. If there's no enclosing full-fledged function, the this in an arrow function will be the global object or undefined, both of which are useless to you. Either use a function to capture the this, or, even better, use the click event's currentTarget to get a reference to the clicked button.
The .innerHTML of an element returns a string, not an element. If it contains a string that can be coerced to a number, explicitly coerce it to a number instead. (If the HTML content is only the string that can be coerced to the number, you should use .textContent instead - only use .innerHTML when deliberately setting or retrieving HTML markup, not plain text)
A better approach would be to pass down the i to onSquareClick instead of using DOM manipulation - using the closure is much easier
let i = 1;
export const Square = ({ onClick }: { onClick: () => void }) => {
i += 1;
if (i > 100) {
i = 1;
}
return (
<DefaultButton styles={factionMapButton} onClick={(e) => { onSquareClick(i, onClick); }}>{i}</DefaultButton>
);
};
const onSquareClick = (number: number, isOpen: () => void) => {
const panelContent = document.getElementById('panelContent');
if (panelContent !== null) {
panelContent.innerHTML = String(number);
}
isOpen();
};
If you're using React, you should not be using vanilla DOM manipulation like panelContent.innerHTML = number; - instead, set React state that the view uses to determine what should exist in that element. Something like
// Parent component:
const [panelContentText, setPanelContentText] = useState('');
// expand as needed for the other components in your app...
return <div id="panelContent">{panelContentText}</div>
<Square setPanelContentText={setPanelContentText} /* and other props */ />
// ...
// Then call the setPanelContentText prop in onSquareClick
const onSquareClick = (number: number, isOpen: () => void, setPanelContentText: React.Dispatch<React.SetStateAction<string>>) => {
setPanelContentText(String(number));
isOpen();
};
I'd recommend looking into an introductory React tutorial, it looks like you might benefit from learning the process within React's ecosystem, rather than trying to mishmash your understanding of vanilla JS's DOM with React.

Can I define a variable within setState in React js?

I am still new to React js.
I am trying to use useState({}) to define an object of objects of orders.
For the newOrderHandler, I am passing the order to be added.
The idea is to add a new object if the order title does not exist and update the amount if the order title already exists in the orders state.
This is the code:
const [orders, setOrders] = useState({});
const newOrderHandler = (newOrder) => {
setOrders(prevOrders => {
console.log('prevOrderss', prevOrders)
// console.log(`prevOrders[newOrder.title]`, prevOrders[newOrder.title])
let newOrders = prevOrders;
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
});
};
The problem here is that although when I log the prevOrders to the console, I get it as I wanted:
However, when I calculate the number of objects in the Navigation component, it just displays 0 always.
This is the code that calculates the number of objects in the Navigation component:
Your Cart <span>{Object.keys(props.orders).length}</span>
This is how I passed the props to the Navigation component:
<Navigation orders={orders} />
This always displays 0. I guess the problem is when defining this: let newOrders in the setOrders function, but I am not sure how to solve it.
Thanks in advance.
The problem is that you React cannot detect that you have changed the object. You need to make a copy, you are passing in the same reference.
newOrders == prevOrders returns true.
What is standard is to make a copy so that you do not mutate the state and react can detect that the object has actually changed.
You can use the spread operator.
let newOrders = { ...prevOrders, [newOrder.title] : { ...prevOrders[newOrder.title] }};
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
Spreading the nested property too because you are mutating its amount property. For every level of nesting you will have to use spread for the property you want to change.

Get text content from React element stored in a variable

Is there a way to get text content from a React element stored in a variable without ref?
There is a functional component, that receives title prop, which contains react element:
function component({ title }) {
const content = title.textContent() // Need Something like this
}
and this title prop might have react node like: <div>Some Title</div>. But I'd like to get only content of the node, in a variable before rendering it. Is it possible?
When I console.log title variable this is the output, The content I want is inside props.children array, so is there a method to get it without traversing through keys:
I've not found a better solution than indeed traversing the object to get the text. In TypeScript:
/**
* Traverse any props.children to get their combined text content.
*
* This does not add whitespace for readability: `<p>Hello <em>world</em>!</p>`
* yields `Hello world!` as expected, but `<p>Hello</p><p>world</p>` returns
* `Helloworld`, just like https://mdn.io/Node/textContent does.
*
* NOTE: This may be very dependent on the internals of React.
*/
function textContent(elem: React.ReactElement | string): string {
if (!elem) {
return '';
}
if (typeof elem === 'string') {
return elem;
}
// Debugging for basic content shows that props.children, if any, is either a
// ReactElement, or a string, or an Array with any combination. Like for
// `<p>Hello <em>world</em>!</p>`:
//
// $$typeof: Symbol(react.element)
// type: "p"
// props:
// children:
// - "Hello "
// - $$typeof: Symbol(react.element)
// type: "em"
// props:
// children: "world"
// - "!"
const children = elem.props && elem.props.children;
if (children instanceof Array) {
return children.map(textContent).join('');
}
return textContent(children);
}
I don't like it at all, and hope there's a better solution.
use https://github.com/fernandopasik/react-children-utilities
import Children from 'react-children-utilities'
const MyComponent = ({ children }) => Children.onlyText(children)
from https://github.com/facebook/react/issues/9255
Thanks #Arjan for the effort and solution, but I have changed something in the component, to get the title in string format.
Now I have added another props to the component: renderTitle which is a function to render custom react title.
So now I am passing title as string:
<Component
title="Some content"
renderTitle={(title) => <div>{title}</div> }
/>
and inside component:
<div>{renderTitle ? renderTitle(title) : title}</div>
With this implementation, I can use title as string to do what I want inside the component, while also supporting custom title render.

Is it possible to customise a Floating Filter to handle Regular Expressions

I would like to be able to use the free text input of the Floating Filter as a regular expression parser. Meaning that, for example, if in my Floating Filter I Have a value "Greg*", the filtered data should contain all values starting with "Greg".
Expected Usage Example
I want to be able to implement my own regular expressions rules to it. I understand I can do it in the filter button on the right but I want to do it on the free text input visible, without having the user clicking on a button.
I solved my question after looking more closely to the ag-grid documentation, thing that I should have done from the start 🙂
Since Floating Filters are actually showing their parent Filter state, one should then implement his own custom Filter and Floating Filter together. So I implemented two classes CustomTextFilter and CustomTextFloatingFilter, implementing respectively IFilterComp and IFloatingFilterComp from the ag-grid library.
export class CustomTextFilter implements IFilterComp{
// IFilterComp method implementations here [...]
}
export class CustomTextFloatingFilter implements IFilterComp{
// IFilterComp method implementations here [...]
}
Within CustomTextFilter class, the most important is to implement the doesFilterPass method, where we can implement our regexp logic. Below is an example with a rule that allows us to search for multiple strings separated by a comma:
doesFilterPass(params: IDoesFilterPassParams): boolean {
var passed = false;
var valueGetter = this.valueGetter;
var filterText = this.eFilterText.value
if (this.isFilterActive()) {
var value = valueGetter(params);
passed = filterText.toLowerCase().split(",").some((word: any) => {
return word !== null && word !== undefined && word.trim() !== ''
&& value.toString().toLowerCase().trim().indexOf(word) >= 0;
});
}
return passed;
}
Then in CustomFloatingFilter we make sure that we that we listen to the input event so we can transfer the value from the floating filter to the parent filter, and then refresh the grid:
init(params: IFloatingFilterParams): void {
// Some gui code here [...]
this.eFilterInput = this.gui.querySelector('input');
this.eFilterText = this.gui.querySelector('#floatingFilterText');
var that = this;
this.eFilterText.addEventListener("input", (event: any) => {
that.filterText = event.target.value;
params.parentFilterInstance((instance: any) => {
instance.setModel({ value: that.filterText });
instance.gridApi.onFilterChanged();
});
});
}
Hope this will help!

Custom word translator in React

Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.

Categories