Template Component doesn't re-render when it is removed from array - javascript

Everything works fine when I append the template onclick. However when the remove events fire it deletes it from the array. It deletes it from the array but when I go to click to append it back to the array, an error fires off. Cannot read property 'content' of null at HTMLButtonElement.. I've double checked my Html and that seems fine. I don't know why it won't re-append or re-render after it was already pushed into the array and then deleted.
// Function to remove item from array onClick
function removeItem(shoppingItems, btns) {
btns.addEventListener('click', (e) => {
let newArr = shoppingItems.filter(item => item.id !== item.id)
shoppingList = newArr
cartMenuItems.innerHTML = newArr
cartItemCount.innerText = newArr.length
return newArr
})
}
// Template Component
btn.addEventListener('click', (e) => {
if (shoppingList.indexOf(cartItem) !== -1) {
return
}
else {
const menuTemplate = document.querySelector('#menu-template').content
const copyMenuTemplate = document.importNode(menuTemplate, true);
copyMenuTemplate.querySelector('.menu-item-title').textContent = cartItem.title;
copyMenuTemplate.querySelector('.menu-price').textContent = cartItem.price;
copyMenuTemplate.querySelector('.menu-img').src = cartItem.image;
const removeBtn = copyMenuTemplate.querySelector('.remove-btn')
cartMenuItems.appendChild(copyMenuTemplate);
shoppingList.push(cartItem)
cartItemCount.innerText = shoppingList.length;
removeItem(shoppingList, removeBtn);
}

I figured it out. I removed the the original menu template from the else statement. I was deleting the model template every time I clicked the button so it couldn't re-render null.

Related

Trying to remove menu visibility when item is clicked

I'm trying to make a responsive menu in a React app, which appears when I click on menu title and disappears when I click on an item, but it seems to not respond really well, Menu disappears sometimes, sometimes it doesn't, it seems like a bug (E.g it always bug the first time I click on an item after refreshing the page)
Edit: the setAside function replaces the Title value with the clicked value, and the title value is added to the reordered list of items, when I click on item that was in the Title last time, classList.remove doesn't work...
this is my code:
const MyComponent = (props) => {
const [title, setTitle] = useState(props.name);
const [lastTitle, setlastTitle] = useState(props.number);
const [listItem, setListItem] = useState(props.list);
const list = (listofItems) => {
const item = [];
listofItems.forEach((value, index) => {
item.push(<li onClick={() => setParam(index, value)} className="items">Item {value}</li>)
});
return item;
}
// This function changes the value of Title to the clicked item value and reorders the values
const setAside = (value) => {
setTitle('Item ' + value);
let newListItem = listItem.filter((item) => item !== value);
newListItem.push(lastTitle);
newListItem.sort(function(a, b){ return a - b });
setlastTitle(value);
setListItem(newListItem);
}
// receives a new page from another component
const setPage = (index, value) => {
for (let x=0; x<3; x++) {
if (listItem[index] == value) {
props.Page("item" + value);
}
}
}
// This is the function called when I click an item
const setParam = (index, value) => {
setAside(value);
setPage(index, value);
}
// Make my menu visible
const mouseClickItem = () => {
var menuItem = document.querySelector(".item-list");
menuItem.classList.toggle("item-responsive");
}
var menuItem = document.querySelector(".item-list");
var items = document.querySelectorAll(".items"); // <---- This var doesn't seems to get the value correctly, but I don't know why
// trying to remove menu visibility when I click on an item
items.forEach(n => n.addEventListener("click", () => {
menuItem.classList.remove("item-responsive"); // <---- not removing items with every click but in some
}))
return (
<aside className="item-choice">
<div onClick={mouseClickItem}> {title} </div>
<ul className="item-list" type="none">
{list(listItem)}
</ul>
</aside>
)
}
Does anyone have any other ideas on how I could remove the menu on click?
I found a solution, I just define menuItem and removed the class inside setAside like this:
const setAside = (value) => {
var menuItem = document.querySelector(".item-list");
...
menuItem.classList.remove("item-responsive");
}
so there is a problem removing the class using forEach or addEventListener, although I don't know what exactly it is.

How to update array in function

i have some problem with my array
let me explain the issue
first i want you guys see my codes to be on the same page
Note: this example code (not full version)
Code:
let checked = []
$.get("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&page=1&per_page=5", status => { createDiv(status, checked)})
function createDiv(status, checked) {
$(".checkbox").click(function (e) {
if (!checked.some(i => i.id.includes($(this).attr('id'))) && checked.length < 3) {
checked.push({
id: $(this).attr('id'),
})
} else if (checked.some(i => i.id.includes($(this).attr('id')))) {
checked = checked.filter(function (a) { return a.id != `${$(e.target).attr('id')}` })
}})}
So when i click on checked it's push to array object and if i click on the same checked button it's filter the array because is already in array.
the problem is the array let checked = [] only update inside the function, what that means if i console.log checked inside function the array is filtered fine but if i console.log checked outside the array is not update
how can i update the array also outside function
i hope you guys have solution for me :)
You declared checked in the function head. So checked exists in local scope. I think if you change the first checked from let checked = [] to var checked = [] and remove the checked in function head it should work.
var checked = []
function createDiv(status) {
$(".checkbox").click(function (e) {
if (!checked.some(i => i.id.includes($(this).attr('id'))) && checked.length < 3) {
checked.push({
id: $(this).attr('id'),
})
} else if (checked.some(i => i.id.includes($(this).attr('id')))) {
checked = checked.filter(function (a) { return a.id != `${$(e.target).attr('id')}` })
}})}
The array will updated outside because it is a reference to a Array. The problem here is you must get data of the array after you click. It mean you must get data in callback at click function. Because this is a async so you only get data at the callback function if you want data is data that after clicked.
let checked = []
$.get("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&page=1&per_page=5", status => { createDiv(status, checked)})
function createDiv(status, checked) {
$(".checkbox").click(function (e) {
if (!checked.some(i => i.id.includes($(this).attr('id'))) && checked.length < 3) {
checked.push({
id: $(this).attr('id'),
})
} else if (checked.some(i => i.id.includes($(this).attr('id')))) {
checked = checked.filter(function (a) { return a.id != `${$(e.target).attr('id')}` })
}
console.log(checked);})}

useState updates correctly when adding to array, but not removing

I have a simple component that allows me to select an item from a list, then remove an item from a list. I display the active list within a parent component. No matter what I do or how I approach it, the removal of an active component is never updated unless they are all in active.
Here is a smaller (yet large snippet) of how it is setup. Below it I describe where I found to be the problem:
const Viewer = () => {
const [items, setItems] = useState(["inactive"]);
return (
<ItemSelect setItems={setItems} selected={items}/>
<DisplayItems items={items}/>
)
}
const ItemSelect = ({setItems, selected}) => {
const handleActiveItems = (activeItems) => {
setItems(activeItems);
}
return (
<SelectItems
handleActiveItems={handleActiveItems}
items={selected}
/>
)
}
const SelectItems = ({handleActiveItems, items}) => {
const [selected, setSelected] = useState([])
useEffect(() => {
setSelected(items);
}, [items]);
const randomTestItem = ["apple", "peach", "orange"];
const handleOnClick = (isSelected, item) => {
let tmpItems = items;
if (isSelected) {
let index = tmpItems.indexOf("inactive");
if (index > -1) {
handleActiveItems([option]);
} else {
handleActiveItems([...selected, option]);
}
} else if (!isSelected) {
let index = tmpItems.indexOf(option);
if (index > -1) {
tmpItems.splice(index, 1);
if (tmpItems.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems([tmpItems]);
}
}
}
}
return (
{
randomTestItem?.map((item,index) => {
return (
<DisplayClickable item={item} onClick={handleOnClick} key={index}/>
)
})
}
)
}
<DisplayClickable item={item} onClick={handleOnClick}/> holds a useState() that toggle from active/inactive.
I've tested this in many different area's I believe the crux of the problem to be here:
} else if (!isSelected) {
let index = tmpItems.indexOf(option);
if (index > -1) {
tmpItems.splice(index, 1);
if (tmpItems.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems([tmpItems]);
}
}
}
specifically:
} else {
handleActiveItems([tmpItems]);
}
When I unselect all the items and switch the array back to "inactive", everything updates instantly and exactly how you would expect. Selecting items always adds to the list correctly, it's removing them that everything goes wonky. I've done a console.log right before calling handleActiveItems() and the tmpItems array is always correct to what it should be. It just never updates the set state.
Within handleActiveItems the log also shows it is receiving the array just before setting it. It just never sets it.
I believe since you are using the splice method, you just modify the existing array and React does not recognize it as "updatable". You can try to use the filter method:
if (index > -1) {
const newArray = tmpItems.filter((_, itemIndex)=> itemIndex !== index)
if (newArray.length === 0) {
handleActiveItems(["inactive"]);
} else {
handleActiveItems(newArray);
}
}
With the code above, filter method will generate a new array.
Give it a try, hopefully it will help =)
update
I've just realized, maybe you don't need the extra [] you are putting into handleActiveItems(). So instead of:
handleActiveItems([tmpItems])
It could be just:
handleActiveItems(tmpItems)
I figured it out.
It all came down to this line:
let tmpItems = items;
Changing to this:
let tmpItems = [...items];
for some reason allowed React to pay more attention and notice that there was in fact a change.
I just changed in my development build and it works without a hiccup.

Updating State React

The following image represents an object with two ui controls that are stored as this.state.controls
Initially the statesValue values are set via data that is received prior to componentDidMount and all is good. However updates to the each of the controls statesValues are sent via an event , which is handled with the following function
const handleValueStateChange = event => {
let controls = Object.entries(this.state.controls);
for (let cont of controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
}
}
}
};
which updates the values happily, however bearing in mind the updated values that change are a subset of this.state.controls, I have no idea how to use this.setState to update that that has been changed.
thanks in advance for any pointers
Instead of using Object.entries try destructuring to keep the reference to the objects.
And have a look at lodash. There are some nice helper functions to iterate over objects like mapValues and mapKeys. So you can keep the object structure and just replace the certain part. Afterwards update the whole state-object with the new one.
const handleValueStateChange = event => {
let {controls} = this.state;
controls = _.mapValues(controls, (cont) => {
const states = cont[1].states;
if (states) {
_.mapValues(states, (contUuid,stateId) => {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
});
}
return cont;
});
this.setState({controls});
};
Code is not tested but it should work like this.
Problem is you're updating an object which you've changed from it's original structure (by using Object.entries). You can still iterate in the same way however you'll need to update an object that maintains the original structure. Try this:
Make a copy of the controls object. Update that object. Replace it in state.
const handleValueStateChange = event => {
// copy controls object
const { controls } = this.state;
let _controls = Object.entries(controls);
for (let cont of _controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
// update controls object
controls[cont[0]].statesValue[stateId] = event.value;
}
}
}
}
}
// replace object in state
this.setState({controls});
};

View not updating on state change

So I got a list of buttons that looks like this
The functionality that I aim for is when you press a button its background will change to another color.
const getUpdatedSelectedItemsArray = (selectedItems, id) => {
selectedItems = []
selectedItems.push(id);
return selectedItems;
};
I use this function to return a list of selected items. Currently I'm only returning one item but I made it an array so I can handle multiple items in the future.
In the render function I have something like this:
<View style={feed_back_page_styles.buttons_wrapper}>
{
feedbackButtons.map((item, i) => (
<TouchableOpacity style={this.state.selectedItems.includes(item.key)?feed_back_page_styles.pressedStyle:feed_back_page_styles.inputStyle}
onPress={()=>this.onButtonPress(item.key)}>
<Text style={this.state.selectedItems.includes(item.key)?feed_back_page_styles.option_text_style_pressed:feed_back_page_styles.option_text_style}>{item.data}</Text>
</TouchableOpacity>
))
}
</View>
feedbackButtons is just an array with a key and text.
The onButtonPress method looks like this:
onButtonPress = (key) =>{
updatedItems = getUpdatedSelectedItemsArray(this.state.selectedItems,key);
this.setState({selectedItems:updatedItems},()=>console.log(this.state.selectedItems));
console.log("Do smth else here");
}
The problem is that the view does not update on state change. When I click the button the state gets updated but the view stays the same.
I think this is wrong
const getUpdatedSelectedItemsArray = (selectedItems, id) => {
selectedItems = []
selectedItems.push(id);
return selectedItems;
};
Since you are passing the this.state.selectedItems as 1st argument from your onButtonPress, actually its not creating new array, but using the same reference of state and state should not be modified directly, always use setState().
So basically what you are doing is :
const getUpdatedSelectedItemsArray = (id) => {
this.state.selectedItems = []
this.state.selectedItems.push(id);
return selectedItems;
};
Which is completely wrong and might be the actual issue.
what you can do instead is :
const getUpdatedSelectedItemsArray = (selectedItems=[], id) => {
const items = [...selectedItems]
items.push(id);
return items;
};
and then :
onButtonPress = (key) =>{
const updatedItems = getUpdatedSelectedItemsArray(key); // since currently you want to keep only 1 item in the list
/* Incase more than 1 items, you can then use this
const updatedItems = getUpdatedSelectedItemsArray(this.state.selectedItems, key);
*/
this.setState({selectedItems:updatedItems},()=>console.log(this.state.selectedItems));
console.log("Do smth else here");
}
Hope this resolves your issue.
Also, if you can share your component, it can help if there is some other issue with your component like if you are using PureComponent.

Categories