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

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.

Related

bad value includes() react

i'm trying to make a function that add a keyword if is not already in the array, if it is shows a error message, but the problem is that the includes() method doesn't work properly, it shows false first (when a word is not in the array), then true (because it is in the array) and then false again if i don't change the word.
I don't know why this is happening but it happens also with indexOf(). Maybe its a react problem with rendering or something like that.
Between if its another way to take an input value and do this, it is welcome
const [repeatedKeyWord, setRepeatedKeyWord] = useState(false)
let keyWords = []
const addKeyword = () => {
let keyword = document.getElementById('keyword').value;
const exist = keyWords.includes(keyword);
console.log(exist)
if (keyword && !exist){
console.log('in')
keyWords.push(keyword)
setRepeatedKeyWord(false)
}
setRepeatedKeyWord(exist? true : false)
console.log(keyWords)
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword} />
You must store your keywords in useState, otherwise you lose the value between re-renders of your component. Thereafter, you can use .includes on your array. React ensures that you'll always have the latest value (e.g. 'snapshot' of your state).
Also note that when you are trying to compute a new state value (i.e. updating your array) you are dependent on the previous value of your state. If that is the case, you can pass a function to the setState function. Have a look at this issue where I have included a link to a working codesandbox for updating previous state values.
As a side note, I would suggest to avoid using let to declare your variables. Only use the let keyword if you are certain that you will re-assign a new value to your variable. Otherwise using const might be better to avoid mistakes.
const [keywords, setKeyWords] = useState([])
const addKeyword = () => {
const keyword = document.getElementById('keyword').value;
return setKeywords((prevState) => {
if (prevState.includes(keyword)) {
return [...prevState]
}
return [...prevState, keyword]
})
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword}

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.

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!

React add a variable name attribute

What is the cleanest way in React to add a variable name attribute?
The end result I want is: <div data-flag>Hello</div> where the name data-flag is stored in a const variable.
The ways I found now are:
const DATA_FLAG = 'data-flag';
const Hello = ({ name }) => {
const dataAttrs = { [DATA_FLAG]: true }
return <div {...dataAttrs}>Hello {name}</div>;
}
Or this one-line version (but I find it less readable):
const Hello = ({ name }) => <div {...{ [DATA_FLAG]: true }}>Hello {name}</div>;
You can play with it in this JSFiddle
These versions work fine if the attribute was variable (true or false) but in my case it's always true so I find it a bit over-killed and the syntax complex.
I would love to know if there is a cleaner way to achieve it.
I know that the best approach is to apply it directly like: <div data-flag>Hello</div> but I really need to store data-flag in a constant (as shared by other components).
You could create a React element without using the JSX syntax. Something like this should work:
const DATA_FLAG = 'data-flag'
const Hello = ({ name }) => {
const dataAttrs = { [DATA_FLAG]: true }
return React.createElement('div', dataAttrs, `Hello ${name}`)
}
This only really makes the way you're passing in the attributes look easier on the eyes though. Don't think you'll get around defining a dataAttrs object for your props.

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

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,
},
],
};

Categories