React JS JSX Show or Hide Based on Another Toggle Component - javascript

I have 2 toggle components that show/hide panels under them which is working fine. However, If I toggle the second panel I need to make sure the first panel is off. Same with the first, I don't want the two panels toggled open at once.
Here is a basic example of what I have.
<ToggleControl
label={__( 'Toggle One' )}
checked={ toggleOne }
onChange={ () => this.props.setAttributes({ toggleOne: ! toggleOne }) }
/>
{ toggleOne ?
<PanelBody>
... Show Panel 1 Stuff
</PanelBody>
: null }
<ToggleControl
label={__('Add Image Divider')}
checked={toggleTwo}
onChange={() => this.props.setAttributes({ toggleTwo: !toggleTwo })}
/>
{ toggleTwo ?
<PanelBody>
... Show Panel 2 Stuff
</PanelBody>
: null }
I can toggle the other panel inside the onChange() of the other toggle by doing this...
onChange={() => this.props.setAttributes({ toggleTwo: !toggleTwo, toggleOne: !toggleOne })}
But I don't want to toggle it if its already off and can't seem to figure it out.
Thanks.

To turn off a panel, you can just set the toggle variable to false.
To turn off the first panel when toggling the second panel, you can do the following:
onChange={() => this.props.setAttributes({ toggleTwo: !toggleTwo, toggleOne: false })}
Similarly, to turn off the second panel when toggling the first panel, you can do the following:
onChange={() => this.props.setAttributes({ toggleOne: !toggleOne, toggleTwo: false })}
This will make sure that when toggling one panel, the other one is always off, so that you will not have both panels toggled on at the same time.

[Edit: Props are read-only, and while my original answer and the accepted answer were identical and do the trick, I'm adjusting my answer to show the "proper" way to do it through manipulating state, not props.]
I don't know why you're tracking the changes on this.props instead of this.state, but here is one way:
To be added in constructor:
this.state = { toggleOne: this.props.toggleOne, toggleTwo: this.props.toggleTwo }
For Toggle One:
onChange={ () => this.setState({ toggleOne: !this.state.toggleOne, toggleTwo: !this.state.toggleOne ? false : this.state.toggleTwo }) }
For Toggle Two:
onChange={ () => this.setState({ toggleTwo: !this.state.toggleTwo, toggleOne: !this.state.toggleTwo ? false : this.state.toggleOne }) }
And of course set the checked attributes to this.state.toggledOne/Two.
Logic:
Set ourselves to our opposite state.
If our current state is off (false) and we're toggled on (!false), turn our neighbor off (false); if we're switching from on to off, leave our neighbor where they are (which in most cases will be off because we were just on).
My additional true/false check at the end vs. the default of false in the accepted answer is meant to catch the case that both toggles were opened on purpose (i.e. programmatically). This may not ever be the case for your scenario, but for others looking for a similar solution who may want this feature, there you have it.

Related

How to properly set focus to a div element in React using createRef

I have a react app that I am working on, and currently, I have a custom-built dropdown that I want to open/close when a user clicks on the trigger(the arrow button), close it when a user selects an option, or close it when a user clicks outside the displayed component.
Here is my code:
For the sake of simplicity, I only added the code that I want help with.
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
showCurrencies: false,
};
this.handleShowCurrencies = this.handleShowCurrencies.bind(this);
}
componentDidMount() {
this.currencyRef = createRef();
}
componentDidUpdate(prevProps, prevState) {
if (this.state.showCurrencies) return this.currencyRef.current.focus();
}
handleShowCurrencies = () => {
this.setState({
showCurrencies: !this.state.showCurrencies,
});
};
render() {
<div className="currency-switch" onClick={this.handleShowCurrencies}>
{currencySymbol}
<span>
<button>
<img src={`${process.env.PUBLIC_URL}/images/arrow.png`} />
</button>
</span>
</div>
{this.state.showCurrencies ? (
<div
className="dropdown"
tabIndex={"0"}
ref={this.currencyRef}
onBlur={this.handleShowCurrencies}
>
{currencies?.map((currency) => (
<div
key={currency.symbol}
className={`dropdown-items ${currencySymbol === currency.symbol ? "selected" : "" }`}
onClick={() => changeCurrencySymbol(currency.symbol)}
>
{`${currency.symbol} ${currency.label}`}
</div>
))}
</div>
) : null}
}
Currently, directing focus to a div element is working fine, and clicking outside the element as well. However, clicking back on the trigger or even selecting an option is not closing the div element. It seems like it is rendering twice(take a closer look on the console): https://drive.google.com/file/d/1ObxU__SbD_Upxr6qcy5eYO4LSy6Mzq9C/view?usp=sharing
Why is that happening? How can I solve it?
P.S: I don't often ask on StackOverflow, so am not familiar with the process. Please bear with me. If you need any other info, I will be more than happy to provide it.

React state material ui map conflict on update

I am having a problem with React state changes accros two different components
The Parent Class has the state of the commands and the functions that change the state
constructor(props) {
super(props);
this.state = {
expanded: false,
addLock: false,
pendingCommand: undefined,
commands: [],
defCommands: [],
};
render() {
return (
<div>
<this.Commands/>
</div>
);
}
The two different components are Material ui Custom Accordions in tabs
Commands = () => {
const tabs = [
<CommandAccordion
cmd={this.state.defcommands}
expan={this.state.expanded} addLock={this.state.addLock}
hdlICh={this.handleInputChange}/>,
<CommandAccordion
cmd={this.state.commands} pendCmd={this.state.pendingCommand}
expan={this.state.expanded} addLock={this.state.addLock}
hdlICh={this.handleInputChange}/>]
}
The Accordion maps through the commands like this:
const commands = props.cmd;
const pendingCommand = props.pendCmd;
{[...pendingCommand === undefined ? [] : [pendingCommand], ...commands].map((obj, index) => (
<CstmAccordion expanded={expanded === obj._id} onChange={handleChange(obj._id, index)}
onClick={() => customOnChange(obj)}
key={'accordion-defcommands-' + index}>
<CstmAccordionDetails>
<Tabs value={tabId}>
<StyledTab label="Settings"/>
<StyledTab label="Advanced settings"/>
</Tabs>
<TabPanel value={tabId} Id={0} index={index} obj={obj} funcs={funcs}
commandV={commandValue}/>
<TabPanel value={tabId} Id={1} index={index} obj={obj} funcs={funcs}
commandV={commandValue}/>
Now the tab pannel has its own state which is set at the beginning and is used in a text field as value
const [commandValue, setCommandValue] = React.useState(obj.command);
<TextField
value={commandValue}
onBlur={funcs.handleInputChange(index, 'command')}
onSelect={() => {
funcs.handleOnFieldSelect(index)
}}
helperText={`What triggers the response? ${!commandValue ? 0 : commandNameValue.length}/40`}
onChange={(event) => {
if (event.target.value.length <= 40)
setCommandValue(event.target.value);
}}/>
Now the problem is if I first load everything it opens the tab for the default command in the parent component and everything looks fine. But if I change the tab to the custom commands, the first command in the Custom-Command-Tab has the same value like the first command in the Default-Command-Tab. (With Tabs I mean const tabs =[... in Commands = () =>{ and not <TabPanel .../>)
And if I change the value in the custom command, save it and change back to the default tab now the default commands have the value of the custom commands.
Also it might be important to now that value only syncs with the other commands with the same index. Which means the second command in the second tab has its own value and not the same value like the command before it - but is still synced with the second command in the first tab.
I think it might have something to do with the order in which the comopnents are rendered but I dont konw.
I've tried managing the state in the Accordion bevore the commands.map function and not in the TabPanel, and changing the state everytime the Accordion is clicked, and giving the TabPanel the commandValue and SetCommandValue and this works in terms of the sync problem, but with that the input into the TextField and has a 2 second input delay per char at worst.
I hope you can understand my problem even if it's a bit much.

Set dynamic state name in React.js

I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen

Dropdown list 'disabled' parameter does not work

I am currently working with javascript/React and I have some problems with the last one.
In my window, I have several buttons, and below, a dropdown list. The idea is to disable this dropdown list. It is only enabled once you click a button, based on the disabled parameter. Among the button parameters, there is also a onClick() which does something else (but at the beginning of this function, I implemented some code so that the value in disabled would change). Which is why I want to use the disabled parameter to enable/disable the dropdown list.
So this is supposed to be easy...
Here is a part of the html :
<DropdownButton
title={"Type : "}
className="sequence-dropdown"
disabled={true}
key="sequence-dropdown"
id="sequence-dropdown"
>
<MenuItem onClick={() => this.changeValue(value1)}>Value 1</MenuItem>
<MenuItem onClick={() => this.changeValue(value2)}>Value 2</MenuItem>
<MenuItem onClick={() => this.changeValue(value3)}>Value 3</MenuItem>
</DropdownButton>
And here is the code supposed to enable the dropdown list, contained in the function changeValue().
var statusDropdown = document.getElementById('sequence-dropdown').disabled;
if (statusDropdown === true) {
console.log(document.getElementById('sequence-dropdown').disabled)
document.getElementById('sequence-dropdown').disabled = false;
console.log(document.getElementById('sequence-dropdown').disabled)
}
else if (statusDropdown === false) {
//console.log(statusDropdownMapType)
document.getElementById('sequence-dropdown').disabled = true;
//console.log(statusDropdownMapType)
}
I did not try it, but I think I could simply use :
document.getElementById('sequence-dropdown').disabled = !document.getElementById('sequence-dropdown').disabled
But that's not the issue here.
My problem is : the button is well disabled at the beginning (with some grey color indicator). When I click a button, the grey color disappears, the component style is normal, and the disabled parameter is well changed.
BUT when I click on the dropdown, the list does not appear... It is like I was just clicking a button, nothing happens...
Does anyone know why ?
I think you should use
document.getElementById('sequence-dropdown').setAttribute("disabled","disabled");
and
document.getElementById('sequence-dropdown').removeAttribute("disabled");
regards Halliballi
If you set disabled={true} everytime the component renders it will be disabled. I would use another approach, like this:
constructor() {
super();
this.state = {disabled: true};
}
...
render () {
const {disabled} = this.state;
return (
<DropdownButton
title={"Type : "}
className="sequence-dropdown"
disabled={disabled}
key="sequence-dropdown"
id="sequence-dropdown">
<MenuItem onClick={() => this.changeValue(value1)}>Value 1</MenuItem>
<MenuItem onClick={() => this.changeValue(value2)}>Value 2</MenuItem>
<MenuItem onClick={() => this.changeValue(value3)}>Value 3</MenuItem>
</DropdownButton>
);
}
Then, since I can see you only change the state from disabled to enabled and viceversa, when you click your button this would be the approach:
this.setState((oldState) => {
return {disabled: !oldState.disabled};
});
This way is more React. Hope it helps.

React: Dropdown component shown once

I have a drop down component that looks like this:
{...}
this.state = {
isVisible: false
}
}
toggleDisplay() {
this.setState({isVisible: !this.state.isVisible});
}
render() {
return (
<div>
<button onClick={this.toggleDisplay()}>click</button>
{this.state.isVisible ? <MenuElements toggleDisplay={this.toggleDisplay} /> : '' }
</div>
)
}
}
"MenuElements" is just a ul that has a li. On another page i am using this component multiple times, so whenever i click on the button, "MenuElements" is shown for each click. The problem is that i want only one component to be displayed. So if a MenuElements component is already displayed, if i click on another button, it closes the previous component, and opens the second one.
How could this be implemented in my code?
Thanks.
You will somehow need to have a single state that defines which MenuItem is displayed. You could go with a global state with something like Redux, but if you are trying to build a reusable component, I guess it'd be best to wrap all of the MenuItem components in a parent component and keep a state there. That, I think, is the React way of doing it. Read this for an idea of how to design components: https://facebook.github.io/react/docs/thinking-in-react.html.
BTW, I think there is an error in the Button onClick handler. It should be:
<button onClick={this.toggleDisplay.bind(this)}> // or bind it somewhere else
Also, the correct way to change state based on previous state is this:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
I'd say this is du to the context of your callbacks. Have you tried forcing the context ?
<div>
<button onClick={this.toggleDisplay.bind(this)}>
click
</button>
{this.state.isVisible ?
<MenuElements toggleDisplay={this.toggleDisplay.bind(this)} />
: '' }
</div>

Categories