React.js onClick in recursive function - javascript

I'm doing a hierarchy tree view in React.js by recursion. I fetch categories from the DB and prepare an array in a structure that each array element is a object that has a prroperty that is an array consisting of his children:
React.js call recursive function in render
I'm already able to generate the view, but now my problem is setting the onClick={
} property for each element.
For some reason, the elements of the first level of the tree works well with the onClick function, but starting at level two, whenever I click any element, the function that is bound to the click is called two, three, four times, dependends on the current element's tree level.
MY function with the onClick:
printTree: function(level){
var self = this;
var level_length = level.length;
if(level_length >= 1){
return(
<ul>
{
level.map(function(category){
return (
<li key={category.fields.name} onClick={self.editCategory.bind(self,category)}><a>{category.fields.name}</a>
{ self.printTree(category.sons_array)}
</li>
)
})
}
</ul>
)
}
}
The input parameter level is always an array..that consists of a level of the tree,a collection of objects that will be processed this iteration.
I think that my problem is calling the function recursively passing self. In the first time that the function is called, self points to the actual this and everything works fine, but starting ata level two in the recursion, self will be pointing to the previous function in the recursive call and not to the this, but I don't know how to fix it.

Related

global list empty when called in another function

I have defined a global list. I have a function that pushes values onto that list, this one works fine. However, I have another function, called after the function pushing the values is done, that needs the values from this list. Unfortunately, when I call the list in this last function, it shows as empty. Why is that?
simplified JS:
var recherche=[];
function clickUpdates() {
switch(count) {
// call recherche.push() multiple times
}
function lancement_scraping() {
console.log(recherche); //shows recherche as empty array
//window.close()
}

How to render multiple HTML parts with a plain Javascript function

This is a static website with hundreds of pages. I need to render elements like a topnav or a newsletter or a strap of content and changing those contents periodically, from JS.
This is what I tried:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
const ids = ['newsletterEs', 'compartirEnFlex', 'infoArticulo', 'infoDeLaWebEnFlexIzq']
function renderComponents(objWithComp, idsArr){
return idsArr.map(function(id){
for(let component in objWithComp){
let arrOfIds = Object.keys(objWithComp);
arrOfIds.map(key => key)
if(id === key){
document.getElementById(id).insertAdjacentHTML('afterbegin', objWithComp[id])
}
}
})
}
renderComponents(components, ids);
Each id has its counterpart in the HTML structure. When I do this individually it works. However, I have to handle this in an elegant way (and there is no possibility for a JS framework like React in this project).
Thanks for the help!
When you run your code, you'll see the error Uncaught ReferenceError: key is not defined in the console.
That's because key in if(id === key) is not defined. The line arrOfIds.map(key => key) returns the same exact array as arrOfIds because Array.prototype.map "returns a new array populated with the results of calling a provided function on every element in the calling array."
Here, you don't assign that new array to a variable, so nothing happens. Even if it was, that new array would be a copy of arrOfIds because your mapping function (key) => key returns key for every key -- meaning that the output is the same as the input.
However, that's not an issue here. If I understand your question correctly, then this demo should show an example of what you're trying to accomplish. If that's what you want to achieve, then here's a solution:
First, you don't need to iterate for component in objWithComponent inside idArr -- you're already doing that in the idArr. You don't need the ids array either, because you can get the keys of the components from the components object using Object.keys().
Let's say your HTML looks something like this:
<div>
<div id="newsletterEs"></div>
<div id="compartirEnFlex"></div>
<div id="infoArticulo"></div>
<div id="infoDeLaWebEnFlexIzq"></div>
</div>
Then, using Object.keys(components) to get an array of the ids of the components that you have, you can map those to HTML tags. In fact, map is not necessary here because map returns a new array, and unless you need that array later, there's no reason to use map. Instead, you can use Object.prototype.forEach.
Here's what that would look like:
const components = {
compartirEnFlex: `<h4>Newsletter</h4>`,
newsletterEs: `<h4>Compartir</h4>`,
}
function renderComponents(objWithComp) {
Object
.keys(components)
.forEach((id) => {
const element = document.getElementById(id)
const component = objWithComp[id]
if (component && element) {
element.insertAdjacentHTML('afterbegin', component)
}
})
}
renderComponents(components)
Then, when you call renderComponents, you can pass just the components argument, and only render the components for which divs with corresponding ids exist with an if statement.

Confusion with the use of higher order function in JS

I am new to JS and was learning how to create and use higher order functions in JS. I faced a bit of confusion with this code:
function elListMap(transform, list) {
// list might be a NodeList, which doesn't have .map(), so we convert
// it to an array.
return [...list].map(transform);
}
function addSpinnerClass(el) {
el.classList.add('spinner');
return el;
}
// Find all the buttons with class 'loader'
const loadButtons = document.querySelectorAll('button.loader');
// Add the spinner class to all the buttons we found.
elListMap(addSpinnerClass, loadButtons);
The question is Why don't we pass argument to addSpinnerClass when addSpinnerClass itself is passed to elListMap. I mean shouldn't we have elListMap(addSpinnerClass(loadButtons), loadButtons); instead of elListMap(addSpinnerClass, loadButtons);
loadButtons is a list of elements. If you passed it to addSpinnerClass it would try to call the classList.add() method of that list of elements.
Since it is a list of elements and not an element, this would error and the program would stop.
If it didn't error, then you would pass the return value of addSpinnerClass (the value you passed into it: i.e. the list) as the first argument to elListMap.
elListMap would then pass that as the first argument to map. The first argument to map needs to be a function, but you would be passing a list of elements. So, again, it would error.

Check if variable is React node or array

I'd like to have a condition that states if a prop is a React node then just place it as a child within a component, and if it's not, take some action to make it into a component.
This way my component will be able to accept this prop as an array of strings, or an array of nodes.
I tried to check if React.PropTypes.node would return a boolean but it doesn't work.
Say I have a module called List and there's a prop called items. I'd like to be able to pass
var items = [
"One",
"Two",
"Three"
]
as well as
var items = function () {
return (
<li>One</li>
<li>Two</li>
<li>Three</li>
)
}
And within the component have some logic that would detect the difference and if it's a plain array (not an array of nodes) be able to map the items.
React has a function just to check if a variable is an element, here's the docs.
React.isValidElement(obj)

How do you set a variable to be an empty, but workable Jquery object?

Outside of a for I declare a variable using var list;. I use this variable inside of my for loop like so:
// add the html to the list
if (list == undefined)
list = item;
else
list.append(item.contents());
item is a cloned jquery object build from a $('list_template').clone(); call (list_template is a div with <li> elements inside). What I am doing is creating a list, which I will then appendTo() where I need it.
Right now this code works fine, but it doesn't seem right to me. Unfortunatly, I cannot seem to figure out how to correctly declare the list variable to be an empty Jquery object. I have tried both:
var list = $([]);
var list = $('');
Both of those cause the append to not work correctly (or as expected) with list.html() being null. Is there a way to initialize my variable to an empty jquery object, so all I have to do is list.append(item.contents()); without the if/else statements?
Edit: Ok to lessen confusion here is the whole javascript function that currently works fine:
var list;
// Loop through all of the objects
var objects = data.objects;
for (x = 0; x < objects.length; x++) {
// Clone the object list item template
var item = $("#object_item_list_template").clone();
// Setup the click action and inner text for the link tag in the template
item.find('a').bind('click', { val: objects[x].Id }, function (e) { ShowObjectDetails(e.data.val); })
.html(objects[x].Name);
// add the html to the list
if (list == undefined)
list = item;
else
list.append(item.contents());
}
// set the list of the topics to the topic list
$("#object_list").empty();
$('<ul>').appendTo("#object_list").append(list.contents());
The object list template is as follows:
<div id="object_item_list_template" style="display:none">
<li class="object_item"></li>
</div>
This all works correctly by cloning the list item, setting up the click action, and adding it to the list on the display.
I am trying to get rid of the if/else statement. I can't just do list.append() because if list is undefined (or not a Jquery object), it throws an exception.
An empty jQuery object is declared as:
$();
Docs for jQuery object creation: http://api.jquery.com/jQuery/
EDIT:
It sounds like you're trying to eliminate the if/else statement by extending list whether or not it has any content.
Is that right?
If so, try something like this:
list = $( list.get().concat(item.get()) );
or
$.extend(list, item);
(Assumes list is starting out as an empty jQuery object.)
EDIT:
Since you're creating elements in a loop, you can always push() then into a jQuery object.
Try something like this:
var list = $(); // Start off with empty jQuery object.
...
list.push(item.find('li').get(0)); // A jQuery object is an Array. You can `push()` new items (DOM elements) in.
...
('<ul>').appendTo("#object_list").append(list);
(I edited from the original to only push the DOM element into the jQuery object.)
Should add the current item in the loop to your list.
You could eliminate the find() calls in your code if you just cloned the children of #object_item_list_template:
$('li', '#object_item_list_template').clone(); // Clone the `li` without the `div`.
Now you have a clone of the li itself. No need to do a find.

Categories

Resources