ReactJS - Elegant way to toggle state - reactjs

I have a simple show / hide style that needs to toggle on a click event. Here is what I have:
constructor(props) {
super(props);
this.state = {iover: 'hide'}
}
handleClick(event) {
// this is wrong, it returns a true or false
this.setState({ iover: !this.state.iover });
// this doesn't toggle
// this.setState({ iover: this.state.iover = 'hide' ? this.state.iover = 'show' : this.state.iover ='hide' });
event.preventDefault()
}
I want to toggle this.state.iover value between 'show' & 'hide'.
What would be the most elegant way to do so.

One way to do this is to keep your state as a boolean true or false then put a ternary operator wherever you want the value "hide" or "show".
For example:
getInitialState: function() {
return {
iover: false
};
},
handleClick: function() {
this.setState({
iover: !this.state.iover
});
},
render: function(){
return (
<div className={this.state.iover ? 'show' : 'hide'}>...</div>
);
}

I think that #mark-anderson's answer is the most "elegant" way, however, the recommended way of doing a state toggling (according to React docs) is:
this.setState(prevState => ({
iover: !prevState.iover
}));
*If you need to store 'show/hide' inside that state, the code would be:
this.setState(prevState => ({
iover: prevState.iover === 'hide' ? 'show' : 'hide'
}));

Although this was a little challenge for me but I ended up like this --
class Toggle extends React.Component{
constructor(props){
super(props);
this.handleToggleVisib = this.handleToggleVisib.bind(this);
this.state = {
visib : false
}
}
handleToggleVisib(){
this.setState({ visib : !this.state.visib });
}
render(){
return(
<div>
<h1>Toggle Built</h1>
<button onClick={this.handleToggleVisib}>
{this.state.visib? 'Hide Button' : 'Show Button'}
</button>
<div>
{this.state.visib && <p>This is a tough challenege</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Toggle />,document.getElementById('app'));

There's a really handy little utility for React called classnames (https://github.com/JedWatson/classnames)
It lets you conditionally render a class, which you can use to handle add the style you need for hiding/showing.
For example, here I'm toggling the state with a function:
state = {
isOpen: false
}
toggleDropdown = () => {
const toggledIsOpen = this.state.isOpen ? false : true;
this.setState({
isOpen: toggledIsOpen
});
}
Then, in the onClick handler for my dropdown , I use classnames to either print class="dropdown" or class="dropdown is-open":
// conditionally add 'is-open' class for styling purposes
const openClass = classNames("dropdown", {
"is-open": isOpen
});
return (
<div className={openClass} onClick={this.toggleDropdown}>[dropdown contents here]</div>
);

constructor(props) {
super(props);
this.state = {iover: false}
}
updateState = () {
this.setState(prevState => ({
iover: !prevState.iover
}));
}
render() {
return (
<div className={this.state.iover ? 'show' : 'hide'}>...</div>
);
}

This is the best I could come up with, was hoping for something shorter:
handleClick(event) {
let show = this.state.iover;
let index = show.indexOf('show');
if (index != -1) {
show = 'hide';
} else {
show = 'show';
}
this.setState({ iover: show });
event.preventDefault()
}

Related

React states remain undefined

I'm trying to set the in property of a bootstrap <Collapse> tag to true on a button click. But when I try to reference my is_open state its undefined.
class Graph extends Component {
constructor(props) {
super (props);
this.state = ({
is_open: false,
});
}
click_open = () => {
console.log(this.is_open); // logs undefined
this.setState({ is_open: !this.is_open });
}
render() {
return (
<div className='container>
<button onClick={this.click_open}>TAB</Button>
<Collapse in={this.is_open}></Collapse>
</div>
)
})
}
No matter what I do my state stays undefined. What am I missing here?
You're missing state
Change your code to be:
click_open = () => {
console.log(this.state.is_open);
this.setState({ is_open: !this.is_open });
}

Trigger a click event on element and then unmount it

I'm trying to implement a suggestion list. I use <input/> followed by a number of <div>s. When the input element is focused, list, or divs, will show; when the input element is not focused, or blurred, the divs are unmounted. I want to print the content in div before the boolean variable isFocused is set to false. But now if I blur the input and click div, the printContent will not be triggered because isFocused has been set to false. Now I am using setTimeout to deal with this problem. Is there a better way to resolve this? Here is the code snippet:
CodeSandbox
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isFocused: false
}
}
handleFocus = () => {
this.setState({ isFocused: true });
}
handleBlur = () => {
setTimeout(() => this.setState({ isFocused: false }), 1000);
}
printContent = () => {
console.log('print content');
}
render() {
console.log(this.state.isFocused)
return(
<div>
<input onFocus={this.handleFocus} onBlur={this.handleBlur} />
{
this.state.isFocused ?
<div key='1' onClick={this.printContent}>content1</div> :
null
}
</div>
);
}
}

How to pass the state true or false from a child to the parent- React

I have two components, one parent (Widgets) and another son (Telefono). The "Telefono" component has the notInCall status and with it I paint or not a certain part of the code.
On the other hand I have the showComponent() function that is in the parent with which I show or not the child component (Telefono) and two other components.
I need to recover from the parent, in the showComponent() function the current status (true or false) of notInCall but I do not know how to do it.
Edit: I think I have not explained well. In the child I use a conditional this.state.notInCall to show or not part of the code. I need to pass the true or false response to the parent. If {this.state.notInCall ? (some code) : (another code)}. If this.state.notInCallon the child is true do one thing and if it is false do another
This is my parent component (Widgets)
class Widgets extends Component {
constructor(props) {
super(props);
this.state = {
componente: 1,
//notInCall: false,
};
this.showComponent = this.showComponent.bind(this);
}
showComponent(componentName) {
/*this.setState({
notInCall: false,
});*/
if(this.state.notInCall === false){
this.setState({
componente: Telefono,
addActive: Telefono,
});
alert(this.state.notInCall + ' running? Componente:' + componentName);
console.log(this.state.notInCall);
}else{
alert('verdad');
this.setState({
componente: componentName,
addActive: componentName,
});
}
console.log(this.state.notInCall);
}
renderComponent(){
switch(this.state.componente) {
case "ChatInterno":
return <ChatInterno />
case "HistorialLlamadas":
return <HistorialLlamadas />
case "Telefono":
default:
return <Telefono showComponent={this.showComponent}/>
}
}
render(){
return (
<div id="bq-comunicacion">
<nav>
<ul>
<li><button onClick={() => this.showComponent('Telefono')} id="mn-telefono" className={this.state.addActive === 'Telefono' ? 'active' : ''}><Icon icon="telefono" className='ico-telefono'/></button></li>
<li><button onClick={() => this.showComponent('ChatInterno')} id="mn-chat" className={this.state.addActive === 'ChatInterno' ? 'active' : ''}><Icon icon="chat-interno" className='ico-chat-interno'/></button></li>
<li><button onClick={() => this.showComponent('HistorialLlamadas')} id="mn-llamadas" className={this.state.addActive === 'HistorialLlamadas' ? 'active' : ''}><Icon icon="historial-llamadas" className='ico-historial-llamadas'/></button></li>
</ul>
</nav>
<div className="content">
{ this.renderComponent() }
</div>
</div>
);
}
}
This is my child component (Telefono)
class Telefono extends Component {
constructor(props) {
super(props);
this.inputTelephone = React.createRef();
["update", "reset", "deleteClickNumber", "closeAlert", "handleKeyPress",].forEach((method) => {
this[method] = this[method].bind(this);
});
this.state = this.initialState = {
notInCall: true,
isRunning: false,
};
}
phoneCall(e){
e.preventDefault();
this.props.showComponent(this.state.notInCall);
if(this.state.inputContent.length < 2){
this.setState({
warningEmptyPhone: true,
});
this.change = setTimeout(() => {
this.setState({
warningEmptyPhone: false
})
}, 5000)
}else if(this.state.inputContent.length >= 2 ){
this.setState({
notInCall: !this.state.notInCall,
isRunning: !this.state.isRunning,
componente: 'Telefono',
},
() => {
this.state.isRunning ? this.startTimer() : clearInterval(this.timer);
//console.log(this.componente);
});
}
}
render(){
return(
<div className="pad sb-content">
{this.state.notInCall
? (
<>
<div className="dial-pad">
<div className="digits">
<Numbers numbers={this.state.numbers}
/>
</div>
</div>
<div className="btn-call call" onClick={this.phoneCall.bind(this)}>
<Icon icon="telefono" className='ico-telefono'/>
<span>LLAMAR</span>
</div>
</>
)
: (
<div className="call-pad">
<div id="ca-number" className="ca-number">{this.state.inputContent}</div>
<TimeElapsed id="timer" timeElapsed={timeElapsed}/>
</div>
)}
</div>
);
}
}
Thanks for the help
You can create a handle in parent, like:
handleNotInCall (notInCall) {
// handle here
}
And pass this handle to child:
<Telefono handleNotInCall={this.handleNotInCall} />
In child you call like this:
this.props.handleNotInCall(<param here>)
UPDATE
on parent:
On Parent:
put notInCall in state
create a handle for notInCall
pass for child handle and state
// state
this.state = {
componente: 1,
notInCall: false,
};
// create a handle
handleNotInCall (notInCall) {
this.setState({notInCall});
}
// pass both for child
<Telefono handleNotInCall={this.handleNotInCall} notInCall={this.state.notInCall}/>
In child, where you do:
this.setState({
notInCall: !this.state.notInCall,
isRunning: !this.state.isRunning,
componente: 'Telefono',
})
// change for
this.props.handleNotInCall(!this.props.notInCall)
this.setState({
isRunning: !this.state.isRunning,
componente: 'Telefono',
})
// where you use for compare
this.state.notInCall ?
// change for:
this.props.notInCall ?
If I understood your problem correctly, the answer of Marcello Silva is correct.
Say you have this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
childAvailable: true,
};
}
handleAvailability = status => {
this.setState({ childAvailable: status });
}
render() {
const { childAvailable } = this.state;
if (!childAvailable) {
return (...); // display whatever you want if children not available
} else {
return (
<div>
{children.map(child => {
<Children
child={child}
statusHandler={this.handleAvailability}
/>
}
</div>
);
}
}
}
and
class Children extends React.Component {
constructor(props) {
super(props);
this.state = {
available: true,
};
}
handleClick = e => {
const status = !this.state.available;
this.setState(prevState => { available: !prevState.available });
// if you call the handler provided by the parent here, with the value
// of status, it will change the state in the parent, hence
// trigger a re render conditionned by your value
this.props.statusHandler(status);
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click me to change status</button>
</div>
);
}
}
Doing this will call the function you passed as prop to your children in your parent. This function sets your state, and therefore triggers a re render once the state has been set, so you'll be rendering whatever you want considering the new value of childAvailable.
EDIT: After seeing the comment on said answer, I'd like to add that you can of course call your handleClick method from your conditions on this.state.notInCall.

Conditionally rendering Font Awesome Icons

I am currently mapping an array to produce several Font Awesome Icons. I have an OnClick method which I am using as a flag: I want to change the icon of only the clicked item. This is my implementation:
<FontAwesomeIcon id={i.key} onClick={this.ToggleIcon} icon={this.state.clicked ? faHeart : faCalendarAlt}/>
ToggleIcon = (e) =>{
if((this.state.clicked)){
this.setState({clicked: true})
}
else if!(this.state.clicked)){
this.setState({clicked: false})
}
}
However, this changes ALL of the icons instead of only the clicked one. How can I accomplish this?
You'll want to track identifiers for your clicked objects in your state, instead of a simple boolean.
If you want only one icon to be 'clicked' at a time:
<FontAwesomeIcon id={i.key} onClick={e => this.ToggleIcon(e, i.key)} icon={this.state.clicked === i.key ? faHeart : faCalendarAlt}/>
ToggleIcon = (e, key) =>{
this.setState(prevState => {
...prevState,
clicked: prevState.clicked === key ? null : key
});
}
If you want to track clicked state for multiple icons:
import * as R from 'ramda';
...
// Init state with empty array
state = {
clicked: [],
// ...other state in this component
}
...
<FontAwesomeIcon id={i.key} onClick={e => this.ToggleIcon(e, i.key)} icon={R.includes(i.key, this.state.clicked) ? faHeart : faCalendarAlt}/>
ToggleIcon = (e, key) =>{
this.setState(prevState => {
if (!R.includes(key, prevState.clicked)) {
return {
...prevState,
clicked: R.append(key, prevState.clicked)
};
}
return {
...prevState,
clicked: R.without(key, prevState.clicked)
};
});
}
I'm using ramda here, but you can use your preferred method of manipulating arrays without mutation. This also assumes that i.key is a unique identifier for each icon.
For the second case, another approach would be to instead wrap each icon in a small component which handles its own state (and would look very similar to what you already have), which is a generally encouraged practice for performance reasons. Whether it's the best approach will depend on the purpose of these icons and the purpose of their 'clicked' state, however.
Are you rendering all those icons on the same component? If so, that's your problem. You're updating a state for the whole component, therefore the state value is equal everywhere, throughout it. So what's the solution? Right, it's to assign different clicked state key/value to each icon.
Array:
class Icons extends React.Component {
constructor(props) {
super(props)
this.state = {
icons: []
}
}
toggleIcon(index, event) {
const iconState = this.state.icons[index] || {}
this.setState({
...this.state,
icons: [
...this.state.icons.slice(0, index),
{
..._currentValue,
clicked: !iconState.clicked
},
...this.state.icons.slice(index + 1)
]
})
}
render() {
return (
<div>
{ ICONS.map((item, index) => {
const iconState = this.state.icons[index] || {}
return (
<FontAwesomeIcon id={index} onClick={this.toggleIcon.bind(this, index)} icon={iconState.clicked ? faHeart : faCalendarAlt} />
)
}) }
</div>
)
}
}
Object:
class Icons extends React.Component {
constructor(props) {
super(props)
this.state = {
icons: {
a: { clicked: false },
b: { clicked: false }
}
}
}
toggleIcon(key, event) {
this.setState({
...this.state,
icons: {
...this.state.icons,
[key]: {
...this.state.icons[key],
clicked: !this.state.icons[key].clicked
}
}
})
}
render() {
return (
<div>
<FontAwesomeIcon id={'a'} onClick={this.toggleIcon.bind(this, 'a')} icon={this.state.icons.a.clicked ? faHeart : faCalendarAlt} />
<FontAwesomeIcon id={'b'} onClick={this.toggleIcon.bind(this, 'b')} icon={this.state.icons.b.clicked ? 'icon 1' : 'icon 2' } />
</div>
)
}
}
learn more:
https://reactjs.org/docs/state-and-lifecycle.html

Change State whenever button is clicked back and forth in React?

So I know how to change state when the button is clicked once, but how would I change the new state back to the previous state when the button is clicked again?
You can just toggle the state.
Here's an example using a Component:
class ButtonExample extends React.Component {
state = { status: false }
render() {
const { status } = this.state;
return (
<button onClick={() => this.setState({ status: !status })}>
{`Current status: ${status ? 'on' : 'off'}`}
</button>
);
}
}
Here's an example using hooks (available in v16.8.0):
const ButtonExample = () => {
const [status, setStatus] = useState(false);
return (
<button onClick={() => setStatus(!status)}>
{`Current status: ${status ? 'on' : 'off'}`}
</button>
);
};
You can change the 'on' and 'off' to anything you want to toggle. Hope this helps!
Here is my example of show on toggle by using React Hook without using useCallback().
When you click the button, it shows "Hello" and vise-versa.
Hope it helps.
const IsHiddenToggle = () => {
const [isHidden, setIsHidden] = useState(false);
return (
<button onClick={() => setIsHidden(!isHidden)}>
</button>
{isHidden && <p>Hello</p>}
);
};
Consider this example: https://jsfiddle.net/shanabus/mkv8heu6/6/
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
buttonState: true
}
this.toggleState = this.toggleState.bind(this)
}
render() {
return (
<div>
<h2>Button Toggle: {this.state.buttonState.toString()}</h2>
<button onClick={this.toggleState}>Toggle State</button>
</div>
)
}
toggleState() {
this.setState({ buttonState: !this.state.buttonState })
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Here we use a boolean true/false and flip between the two states. If you are looking to use some other custom data as your previous state, just create a different variable for that.
For example:
this.state = { previousValue: "test", currentValue: "new thing" }
This will toggle to previous and new value :
constructor() {
super();
this.state = {
inputValue: "0"
}
}
render() {
return (
<div>
<input
type="button"
name="someName"
value={this.state.inputValue}
onClick={() =>
this.state.inputValue === "0"
? this.setState({
inputValue: "1"
})
:
this.setState({
inputValue: "0"
})
}
className="btn btn-success"
/>
</div>
)
}
Description :
If the current value = 0, then set the value to 1, and vice versa.
This is useful if you have a lot of inputs. So, each input has a different state or condition.
You must save the previous state. You could even make previous state part of your actual state - but I'll leave that as an exercise for the OP (Note: you could preserve a full history of previous states using that technique). Unfortunately I cannot yet write examples from the top of my head using the new hooks feature:
class MyComponent extends ReactComponent {
prevState = {}
state = {
isActive: false,
// other state here
}
handleClick = () => {
// should probably use deep clone here
const state = Object.assign({}, this.state);
this.setState(state.isActive ? this.prevState : Object.assign(state, {
isActive: true,
// other state here
});
this.prevState = state;
}
render() {
return <button onClick={this.handleClick}>Toggle State</button>
}
}
in state:
this.state = {toggleBtn: ""}
in your button:
<button key="btn1" onClick={() => this.clickhandler(btn1)}>
{this.state.toggleBtn === ID? "-" : "+"}
</button>
in your clickhandler:
clickhandler(ID) {
if (this.state.toggleBtn === ID) {
this.setState({ toggleBtn: "" });
} else {
this.setState({ toggleBtn: ID});
}

Resources