ReactJS - Avoid resetting state when updating an object within state - javascript

I am fetching some data from the server to populate a list of items, and each item got a onClick event binded to the items id, that changes the UI to be disabled when clicked.
My problem is that the UI changes to disabled perfectly on the first click, but when I go on to click on the next item it resets the first one, so there is only one button disabled at a time. How do I make it so I can disable all the items I want, without resetting the previous state?
class Video extends Component {
constructor () {
super()
this.state = {
isDisabled: false
}
}
handleClick(frag, voted, event){
event.preventDefault()
this.setState({
isDisabled: {
[frag]: true
}
})
}
Snippet of what I return in the UI that changes the disabled button
<button onClick={this.handleClick.bind(this, frags.id, frags.voted)} disabled={this.state.isDisabled[frags.id]} className="rating-heart-2">
<i className="fa fa-heart" aria-hidden="true"></i>
</button>
I would really appreciate all tips!

You are completely replacing isDisabled each time you update state, but you really just want to add a new property to it. Try the following:
handleClick(frag, voted, event){
event.preventDefault();
let {isDisabled} = this.state;
isDisabled[frag] = true;
this.setState({isDisabled});
}

Related

onClick doesn't work on first click after rerender

const NavigationButtons = ({onBtnClicked, btnClass, label, route, btnAct}) => {
return (
<p
className={`btn ${btnClass} ${btnAct}`}
onClick={() => onBtnClicked(route)}>
{label}
</p>
);
};
This is my button component, I'm giving it to another component as btns1 props
{!isSigned?btns1:windowPixel?btns1:null}
Basically, when isSigned is false, btns1 is rendered. There's really no problem here. When isSigned is true, it checks if windowPixel is true, this is changed to true by App.js as a state by measuring the current window. It works perfectly unless I click the button. Resize the window where windowPixel will be false, then on my first click, it doesn't trigger onClick. After that onClick works again.
componentDidMount() {
if (window.matchMedia(`(max-width: 990px)`).matches) {
this.resizeWindow(true);
}
window.addEventListener("resize", () => this.resizeWindow(window.matchMedia(`(max-width: 990px)`).matches));
}
This is what checks the window size for windowPixel. chatScroll, is for the panel that expands when btn1 is clicked, btnAct is just for a css that change the color of the button while the panel is expanded. For now, I've put click(), like a bandaid.
resizeWindow = (windowPixel) => {
const {chatScroll, btn1Act} = initialState;
if (windowPixel !== this.state.windowPixel) {
if (windowPixel === false) {
if (this.state.isSigned) {
document.getElementById('btn1').click();
this.setState({chatScroll, btn1Act});
}
}
this.setState({windowPixel});
}
};
The Reason Is Simple Brother in First Click Your Object or function or variable what ever it is , Just Initialize in first click and when you click second time it will Work as per your code.

React Can I set button to do different things based on dropdown menu selection?

I have a dropdown menu on a form. Once the user chooses a selection, I'd like for the Submit button to do different requests based on the dropdown menu item selection. For example, if user chooses Apples, then Submit button fires a function1, while if user chooses Bananas, then Submit button fires a function2.
I have a parent component that looks like this:
class Container extends React.PureComponent {
_onSubmit = (date) => {
this.props.function1({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
};
render() {
return (
<div>
<DownloadForm
dataSetName="Apples"
buttonText="Request Report"
onSubmit={this._onSubmit}
/>
</div>
);
}
}
Now, my DownloadForm component, which is nested here, will have a dropdown menu, and based on the dropdown menu's item, will have the onSubmit do something with a function2, which takes different parameters too.
I'm not sure how to do design this. I have 1 page as above, with the parent component that passes the onSubmit to it's nested component, and fires away to function1. I have another page as above, with the parent component also passing an onSubmit to the nested component, which fires away to function2.
How do I make it so that the dropdown menu item selection, inside the nested component, will then change it's button's onSubmit to fire away to function1 or function2 based on the dropdown menu item selected?
I hope my issue is clear.
Have a state that records the value of the dropdown selection inside the parent component. Inside _onSubmit() you check the state.
if (this.state.selection == "Apples") {
function1(param1, param2)
}
Here is a quick bit of code I put together that should get you started. It also takes into account some of #Mikes' comments
class Container extends React.Component {
constructor(props){
super(props);
this.state = {
selection: null
}
}
onSubmit = (date) => {
if (this.state.selection == "Apples") {
this.props.function1({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
}
if (this.state.selection == "Bananas") {
this.props.function2({
paramA: this.props.paramA,
paramB: this.props.paramB,
});
}
};
handleSelectionChange = (selection) => {
this.setState({selection: selection})
}
render() {
return (
<div>
<Dropdown
onChange={this.handleSelectionChange}
/>
<DownloadForm
dataSetName="Apples"
buttonText="Request Report"
onSubmit={this._onSubmit}
/>
</div>
);
}
}

React trouble with event.stopPropagation()

I have two components here, the first one is a table, and I have an on-click event attached to one of the <td>'s in every row that summons a little tooltip-like window:
<td onClick={ () => loadSelectorWindow(p.product_id) }>
{
p.selectorActive &&
<SelectorWindow
cancelWindow={this.cancelSelectorWindow}
product_id={p.product_id}/>
}
</td>
The function bound to the <td> click will search through all products in state and flip a boolean on the selected product to display the tooltip.
loadSelectorWindow = (product_id) => {
this.setState({ products: this.state.products.map( p => {
if (p.product_id == product_id) {
p.variationSelectorActive = true
} else {
p.variationSelectorActive = false
}
return p
})})
}
However, the tooltip also needs a button with a window cancel event linked to it:
// within <SelectorWindow />
<p onClick={ () => {cancelWindow(event)} }> X </p>
This function cycles through state and sets all of the display booleans to false.
cancelSelectorWindow = (event) => {
event.stopPropagation()
this.setState ({ products: this.state.products.map( p => {
p.variationSelectorActive = false
return p
})})
}
Putting breakpoints in the code I can see that the cancel button is correctly calling the cancel function and setting the displayTooltip boolean to false, temporarily. The problem is, the loadSelectorWindow is ALSO getting fired when the cancelWindow button is clicked, and the boolean is set back to true DX.
This is why I attempted to put the event.stopPropagation call in there but obviously something is still calling it. There is no other place in my code that the loadSelectorWindow function is mentioned... Any ideas how I can stop it from getting called?
I forgot to pass event to the cancelWindow callback function. React why is your syntax so confusing sometimes...
Fix:
<p onClick={ (event) => {cancelWindow(event)} }> X </p>
You have one html element nested inside the other, so if you click the inner one then you will receive onClick events for both. So that is what you are getting. You need to redesign the layout of the page so that does not happen.

How to remove element when clicking outside the element in javascript?

I have created a dropdown menu for my web app and when clicking on a icon i'm opening my dropdown menu. I want to remove the dropdown menu when i click anywhere other than the dropdown menu. My current approach removes the element when clicking outside the element. But i cannot open the dropdown menu when clicking on the icon after that. How can i fix it?
class DropDown extends Component {
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
this.state = {
openmenu:false
}
};
myFunction(e) {
e.stopPropagation();
this.setState({
openmenu: !this.state.openmenu
})
render() {
window.onclick = function (event) {
if (!event.target.matches('myDropdown')) {
document.getElementById('myDropdown2').remove();
}
}
return (
<div className="dropdown small_font" id="myDropdown" >
<i className="fa fa-cog user_settings_icon" style={{marginTop: '6px'}} onClick={this.myFunction}
aria-hidden="true"></i>
{this.state.openmenu?
<div className="dropdown-content" id="myDropdown2">
<a className="dropdown_item"><i className="fa fa-trash-o margin_right10px" aria-hidden="true"></i>Delete</a>
<a className="dropdown_item"><i className="fa fa-flag-o margin_right10px" aria-hidden="true"></i>Report</a>
</div>:null
}
</div>
);
}
}
The error i'm getting when clicking on the icon for the second time
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
If you don't want to keep track of adding and removing click events on unmount and a solution that works across all browsers id recommend using a library. Ive used https://github.com/Pomax/react-onclickoutside and it works very well, heres a snippet.
import React, { Component } from "react";
import onClickOutside from "react-onclickoutside";
class MyComponent extends Component {
handleClickOutside = evt => {
// ..handling code goes here...
};
}
export default onClickOutside(MyComponent);
If you dont want to use a library use the onBlur
class MyComponent extends Component {
handleClickOutside = evt => {
// ..handling code goes here...
};
render(){
<div onBlur={this.handleClickOutside}>
<SomeChild />
</div>
}
}
Lastly your using React wrong, your using it as if it was jquery which it is not. you dont remove anything manually. You have state that you update when to close the dropdown and when to open it.
Using events such as window.onclick in render method is not a good practice since every time you update your state this event is going to be instantiated.
I also noticed you have created the state prop openmenu and myFunction but you not using them properly.
My suggestion is to attach DOM events in ReactJS's lifecycle events:
componentDidMount() {
window.addEventListener('onClick', this.myFunction)
}
componentWillUnmount() {
// Make sure to remove such event when your component is unmounted
window.removeEventListener('onClick', this.myFunction)
}

REACT: toggle class onClick, and call other function

I am new to react so please be patient - I'm sure this is a simple problem but I am having a lot of trouble figuring out a solution.
I have a set of buttons, when they are clicked I add an 'active' className to that button and remove it from any other buttons that might be active.
I also need to open a panel with content based on which button is clicked.
So far I have managed to toggle the className of the clicked button, but can't figure out how to only apply it to the button that is clicked (see code below)
<a onClick={this.buttonClick(1)} className={this.state.isButtonActive ? 'active' : null}>button text</a>
<a onClick={this.buttonClick(2)} className={this.state.isButtonActive ? 'active' : null}>button text</a>
and the function to toggle the active state and open appropriate panel based on the button clicked:
buttonClick(buttonNumber) {
this.setState(prevState => ({
isButtonActive: !prevState.isButtonActive
}));
openPanel(buttonNumber)
}
I have experimented with creating a child component for the button and toggling the className within the component, but then I can't trigger the openPanel() function as it is in the parent component.
I hope that makes sense - thank you in advance :)
the problem is that you are sharing the same state for both buttons, so when you change it for one, it changes for the other. You should wrap your buttons in different components so that they have different state.
If you need a callback in the parent component to be called, pass it to the button components so that they can trigger that as well.
The button could look like this:
class Button extends React.Component {
constructor () {
super()
this.state = { isButtonActive: false }
this.onClick = this.onClick.bind(this)
}
onClick () {
this.setState({
isButtonActive: !this.state.isButtonActive
})
this.props.openPanel(this.props.buttonNumber)
}
render () {
return (
<button onClick={this.onClick()} className={this.state.isButtonActive ? 'active' : null}>button text</a>
)
}
}
how the parent component could look like:
class Parent extends React.Component {
onButtonClicked (number) {
console.log(`Button ${number} was clicked`)
}
render () {
return (
<div>
<Button buttonNumber={1} openPanel={this.onButtonClicked} />
<Button buttonNumber={2} openPanel={this.onButtonClicked} />
</div>
)
}
This happens because of the common state for both the button.
Instead of storing a boolean value for active, you can store the numeral like
<a onClick={this.buttonClick(1)} className={this.state.isButtonActive === 1 ? 'active' : null}>button text</a>
<a onClick={this.buttonClick(2)} className={this.state.isButtonActive === 2 ? 'active' : null}>button text</a>
and Click action to
buttonClick(buttonNumber) {
this.setState({
isButtonActive: buttonNumber
});
openPanel(buttonNumber)
}

Categories

Resources