How to get 'this' on element returned from react render method - javascript

I have react class. This class in it's render method returns multiple HTML elements. On some of this elements I need to use function that will use 'this' of this element, not whole class.
// imports
class App extends Components {
render() {
const arr = ['one', 'two', 'three'];
const newArr = arr.map((val) => {
return <li id={IdFactory.register(val, getPath(THIS_LI))} key={val}>{val}</li>
})
return (
<ul>{newArr}</ul>
)
}
}
IdFactory.register takes a string and path to this element. Any idea how I can distinguish this element for the function?

When you use arrow function it will not create its this context
Tip: If you ever want to find the context of this you can console.log it where you want to find
console.log(this)//Put where you want to find.

Related

Output each child with a wrapper from React.Children array

I have this reusable component.
export const ListItems = ({ controls, children }) => {
const content = controls ? <PrivateComponent>{ children }</PrivateComponent> : children;
return <ul>{ content }</ul>
}
Seems pretty-straight forward. The idea is that, in PrivateComponent, I wanna wrap each of the children with an extra wrapper, something like this:
export const PrivateComponent = ({ children }) => {
const _children = React.Children.toArray(children);
return (
<div>{ _children.map(child => <SomeWrapper>{ child }</SomeWrapper> ) }</div>
);
}
My question is, is it correct to render child this way, or should I use cloneElement? Also, what should I use for the key of SomeWrapper in the map function?
is it correct to render child this way, or should I use cloneElement?
You can use the child instance directly, there is no problem there. You need cloneElement only if you want to change / add any props or swap out its own children, or if you need a duplicate because you intend to insert "the same child" in two different location in your DOM.
what should I use for the key of SomeWrapper in the map function?
You can use React.Children.map(children, function[(thisArg)]) (doc) which will automatically add keys.

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.

ReactJS: Join map output with concatenating value

In my ReactJS application I am getting the mobile numbers as a string which I need to break and generate a link for them to be clickable on the mobile devices. But, instead I am getting [object Object], [object Object] as an output, whereas it should be xxxxx, xxxxx, ....
Also, I need to move this mobileNumbers function to a separate location where it can be accessed via multiple components.
For example: Currently this code is located in the Footer component and this code is also need on the Contact Us component.
...
function isEmpty(value) {
return ((value === undefined) || (value === null))
? ''
: value;
};
function mobileNumbers(value) {
const returning = [];
if(isEmpty(value))
{
var data = value.split(',');
data.map((number, index) => {
var trimed = number.trim();
returning.push(<NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>);
});
return returning.join(', ');
}
return '';
};
...
What am I doing wrong here?
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
First question:
What am I doing wrong here?
The issue what you have is happening because of Array.prototype.join(). If creates a string at the end of the day. From the documentation:
The join() method creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string. If the array has only one item, then that item will be returned without using the separator.
Think about the following:
const navLinks = [{link:'randomlink'}, {link:'randomlink2'}];
console.log(navLinks.join(','))
If you would like to use concatenate with , then you can do similarly like this:
function mobileNumbers(value) {
if(isEmpty(value)) {
const data = value.split(',');
return data.map((number, index) => {
const trimed = number.trim();
return <NavLink to={`tel:${trimed}`} key={index}>{trimed}</NavLink>;
}).reduce((prev, curr) => [prev, ', ', curr]);
}
return [];
};
Then you need to use map() in JSX to make it work.
Second question:
Is there any way to create a separate file for the common constants / functions like this to be accessed when needed?
Usually what I do for constants is that I create in the src folder a file called Consts.js and put there as the following:
export default {
AppLogo: 'assets/logo_large.jpg',
AppTitle: 'Some app name',
RunFunction: function() { console.log(`I'm running`) }
}
Then simply import in a component when something is needed like:
import Consts from './Consts';
And using in render for example:
return <>
<h1>{Consts.AppTitle}</h1>
</>
Similarly you can call functions as well.
+1 suggestion:
Array.prototype.map() returns an array so you don't need to create one as you did earlier. From the documentation:
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
I hope this helps!

How to replace the value of an array of objects in React

I have this function that returns an array of objects, every object represent a sticky, what I want is to change the value of "content" everytime I click one of the stickies
handleStickyEdition = (index) => {
const { currentStage } = this.props
const stickies = currentStage.get('stickies')
const updatedStickies = [...stickies]
console.log(updatedStickies)
}
And the result of calling the console.log is this array of objects:
If I do console.log(updatedStickies[index].get('content')) I will get the content of the object I want to change. For example name 3.
How can I replace this content with an empty string? in other words, if I click the object in the position 0, how can I make name 3 equals to ''
I would suggest using a map like so.
this.setState({
updatedStickies: this.state.updatedStickes.map(sticky => ({
...sticky
content: sticky.id === idOfStickyWeWantToUpdate ? "" : "content"
}))
});
I see you are reading stickies from props, I would suggest having a function in your parent component to run the above code which you can call from your child component if need be.

ReactJS: using object ref as {key}?

How can I use an object reference as {key} in ReactJS?
I've tried this:
let ruleList = _.map(this.state.rules, function(rule) {
return <RuleList rule={rule} key={rule} />
});
but this ends up being printed in the console:
Warning: flattenChildren(...): Encountered two children with the same
key, .0:$[object Object]. Child keys must be unique; when two
children share a key, only the first child will be used.
Any way to get around this without hacks such as generating IDs for each item?
I had a similar situation where the object has no unique id.
I ended up generating ids for the items based on object references:
let curId = 1;
const ids = new WeakMap();
function getObjectId(object) {
if (ids.has(object)) {
return ids.get(object);
} else {
const id = String(curId++);
ids.set(object, id);
return id;
}
}
// Usage
<RuleList rule={rule} key={getObjectId(rule)} />
I know you mentioned that you don't want to generate ids, but I thought I'd share this since it is generic and doesn't depend on any properties in your object.
I created a lib intended to solve this problem. It's based on the idea suggested by #amann (using a weakMap). It provides a hook, so that the weakMap is created and destroyed with the component.
Have a look to https://www.npmjs.com/package/react-key-from-object
import { useKeyGen } from 'react-key-from-object'
const DogList = () => {
const keyGen = useKeyGen();
return (
<ul>
{dogs.map((dog) => (
<li key={keyGen.getKey(dog)}>
{dog.name}
-
{dog.age}
</li>
))
</ul>
);
}
Update
Objects cannot be used as keys. React js requires a key to be a string or a number and should be unique.
IMO there are two ways to solve this problem (open to suggestions)
Option 1
Iterate through the array and create a unique index
var rules = data.rules;
for(var i=0;i<rules.length;i++){
data.rules[i].key = i;
}
Use this key in _.map
let ruleList = _.map(this.state.rules, function(rule) {
return <RuleList rule={rule} key={rule.key} />
});
Option 2
Maintain a array of indices of rule objects which are not deleted.
var N = rules.length;
var arrayOfRules = Array.apply(null, {length: N}).map(Number.call, Number);
When you delete an item remove it using .splice.
The component should look like this
let ruleList = _.map(this.state.rules, function(rule, index) {
return <RuleList rule={rule} key={arrayOfRules[index]} />
});
----
Since rule object has no property which is unique and key needs to be unique, add the index parameter that comes in the map method.
let ruleList = _.map(this.state.rules, function(rule, index) { // <--- notice index parameter
return <RuleList rule={rule} key={index} />
});
Why do you insist on using the rule object as the key?
The easiest solution would be to use an index, returned from the underscore map function, instead. Like this:
let ruleList = _.map(this.state.rules, function(rule, index) {
return <RuleList rule={rule} key={index} />
});
Here is a similar topic related to the map function.

Categories