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
Related
Like the title says I want to reset the font size of the text when the block type is changed.
What I mean by that is, when let's say I have the block type of unstyled and I change the font size from 16 (default) to the 10. The font size should reset when I change the block type to for example H1, but for me it doesn't.
What I realized is that this library seems to put the font size globally, meaning for the whole document, does anyone know how to reset the font size after the block type changes.
Here is what I have done so far:
I have made a custom font size library which is inside the toolbar:
export default class CustomFontSize extends Component {
constructor(props) {
super(props);
this.state = {
expanded: false,
hover: false,
currentIndex: 0,
};
}
componentDidMount() {
const { editorState } = this.props;
const { fontSize } = this.props.currentState;
const blockType = RichUtils.getCurrentBlockType(editorState);
document.addEventListener('click', this.onDocumentClick);
getListFontSize(blockType, fontSize);
}
componentWillUnmount() {
document.removeEventListener('click', this.onDocumentClick);
}
onDocumentClick = event => {
const { expanded } = this.state;
const dropdown = document.getElementsByClassName('rdw-fontsize-dropdown')[0];
if (expanded && dropdown && !dropdown.contains(event.target)) {
this.setState({
expanded: false,
});
}
};
render() {
const { editorState, onChange } = this.props;
const { fontSize } = this.props.currentState;
const { options } = this.props.config;
const { currentIndex, hover, expanded } = this.state;
const blockType = RichUtils.getCurrentBlockType(editorState);
return (
<div className="rdw-fontsize-wrapper" aria-label="rdw-font-size-control">
<div className="rdw-dropdown-wrapper rdw-fontsize-dropdown" aria-label="rdw-dropdown" aria-expanded="false">
<a className="rdw-dropdown-selectedtext" title="Font Size" onClick={() => this.setState({ expanded: !expanded })}>
{fontSize !== undefined ? (
<span>{fontSize}</span>
) : (
<span>{getInitalFontSize(blockType)}</span>
)}
<div className={expanded ? 'rdw-dropdown-carettoclose' : 'rdw-dropdown-carettoopen'} />
</a>
{expanded && (
<ul className="rdw-dropdown-optionwrapper">
{options.map((size, index) => (
<li
key={size}
className={
`rdw-dropdownoption-default rdw-fontsize-option ${
currentIndex === index &&
hover === true
? 'rdw-dropdownoption-highlighted '
: ''
}${fontSize === size ? 'rdw-dropdownoption-active' : ''}`
}
onClick={() => {
onChange(size);
this.setState({ expanded: false });
}}
onMouseEnter={() => this.setState({ hover: true, currentIndex: index })}
onMouseLeave={() => this.setState({ hover: false })}
>
<span>{size}</span>
</li>
)
)}
</ul>
)}
</div>
</div>
);
}
}
One solution that I thought might work is if I put this inside the componentDidMount():
if (fontSize !== undefined && editorState.getLastChangeType() === 'change-block-type') {
this.props.onChange(20) // I put the 20 as an example
}
But this breaks my site as the onChange function, which is a function from the library (reference: https://jpuri.github.io/react-draft-wysiwyg/#/docs , scroll down to the section: Using custom react component for pre-built toolbar options), runs infinitely.
The fix "works" when I put it inside componentDidUpdate(), but I need to click on the editor for it to update which is not what I want.
If anyone knows how to fix this, or if it's not possible with this library, I would really appreciate it!
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});
}
For this application, clicking a listed item once should create a button component underneath this listed item. Clicking the button should cause this listed item to be deleted.
I am currently facing difficulty trying to 'delete' the listed item after the button is clicked. Here is the code that went wrong (this is found in CountdownApp component) :
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList) // countdownList array is correct
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns) // this.state.countdowns does not match countdownList
console.log(countdownList) // countdownList array is still correct
})
}
In the code above, I removed the item to be deleted from countdownList array with splice and tried to re-render the app with setState. However, the new state countdowns do not reflect this change. In fact, it returns the unedited state.
I have also tried the following:
handleDelete(index) {
this.setState({
countdowns: [] // just using an empty array to check if setState still works
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
In the code above, I tried setting state to be an empty array. The console log for this.state.countdowns did not print out an empty array. It printed out the unedited state again
This is the only event handler that isn't working and I have no idea why (main question of this post) :/
If I have 'setstate' wrongly, why does the other 'setState' in other parts of my code work?? (I would like to request an in-depth explanation)
This is all my code for this app (its a small app) below:
import React from 'react'
import ReactDOM from 'react-dom'
class DeleteButton extends React.Component {
render() {
return (
<ul>
<button onClick={this.props.onDelete}>
delete
</button>
</ul>
)
}
}
class Countdown extends React.Component {
render () {
//console.log(this.props)
return (
<li
onClick={this.props.onClick}
onDoubleClick={this.props.onDoubleClick}
>
{this.props.title} - {this.props.days}, {this.props.color}
{this.props.showDeleteButton ? <DeleteButton onDelete={this.props.onDelete}/> : null }
</li>
)
}
}
const calculateOffset = date => {
let countdown = new Date(date)
let today = new Date
let timeDiff = countdown.getTime() - today.getTime()
let diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24))
return diffDays
}
class CountdownList extends React.Component {
countdowns() {
let props = this.props
// let onClick = this.props.onClick
// let onDoubleClick = this.props.onDoubleClick
let rows = []
this.props.countdowns.forEach(function(countdown, index) {
rows.push(
<Countdown
key={index}
title={countdown.title}
days={calculateOffset(countdown.date)}
color={countdown.color}
showDeleteButton={countdown.showDeleteButton}
onDelete={() => props.onDelete(index)}
onClick={() => props.onClick(index)}
onDoubleClick={() => props.onDoubleClick(index)}
/>
)
})
return rows
}
render() {
return (
<div>
<ul>
{this.countdowns()}
</ul>
</div>
)
}
}
class InputField extends React.Component {
render() {
return (
<input
type='text'
placeholder={this.props.placeholder}
value={this.props.input}
onChange={this.props.handleInput}
/>
)
}
}
class DatePicker extends React.Component {
render() {
return (
<input
type='date'
value={this.props.date}
onChange={this.props.handleDateInput}
/>
)
}
}
class CountdownForm extends React.Component {
constructor(props) {
super(props)
this.state = {
title: this.props.title || '',
date: this.props.date || '',
color: this.props.color || ''
}
}
componentWillReceiveProps(nextProps) {
this.setState({
title: nextProps.title || '',
date: nextProps.date || '',
color: nextProps.color || ''
})
}
handleSubmit(e) {
e.preventDefault()
this.props.onSubmit(this.state, this.reset())
}
reset() {
this.setState({
title: '',
date: '',
color: ''
})
}
handleTitleInput(e) {
this.setState({
title: e.target.value
})
}
handleDateInput(e) {
this.setState({
date: e.target.value
})
}
handleColorInput(e) {
this.setState({
color: e.target.value
})
}
render() {
return (
<form
onSubmit={(e) => this.handleSubmit(e)}
>
<h3>Countdown </h3>
<InputField
placeholder='title'
input={this.state.title}
handleInput={(e) => this.handleTitleInput(e)}
/>
<DatePicker
date={this.state.date}
handleDateInput={(e) => this.handleDateInput(e)}
/>
<InputField
placeholder='color'
input={this.state.color}
handleInput={(e) => this.handleColorInput(e)}
/>
<button type='submit'>Submit</button>
</form>
)
}
}
class CountdownApp extends React.Component {
constructor() {
super()
this.state = {
countdowns: [
{title: 'My Birthday', date: '2017-07-25', color: '#cddc39', showDeleteButton: false},
{title: 'Driving Practice', date: '2017-07-29', color: '#8bc34a', showDeleteButton: false},
{title: 'Korean BBQ', date: '2017-08-15', color: '#8bc34a', showDeleteButton: false}
]
}
}
handleCountdownForm(data) {
if (this.state.editId) {
const index = this.state.editId
let countdowns = this.state.countdowns.slice()
countdowns[index] = data
this.setState({
title: '',
date: '',
color: '',
editId: null,
countdowns
})
} else {
data.showDeleteButton = false
const history = this.state.countdowns.slice()
this.setState({
countdowns: history.concat(data),
})
}
}
handleDelete(index) {
console.log('in handleDelete')
console.log(index)
let countdownList = this.state.countdowns.slice()
countdownList.splice(index, 1)
console.log(countdownList)
this.setState({
countdowns: countdownList
}, function() {
console.log('after setState')
console.log(this.state.countdowns)
})
}
handleCountdown(index) {
const countdownList = this.state.countdowns.slice()
let countdown = countdownList[index]
countdown.showDeleteButton = !countdown.showDeleteButton
this.setState({
countdowns: countdownList
})
}
handleDblClick(index) {
const countdownList = this.state.countdowns
const countdown = countdownList[index]
this.setState({
title: countdown.title,
date: countdown.date,
color: countdown.color,
editId: index
})
}
render() {
return (
<div>
<CountdownForm
title={this.state.title}
date={this.state.date}
color={this.state.color}
onSubmit={(data) => {this.handleCountdownForm(data)}}
/>
<CountdownList
countdowns={this.state.countdowns}
onDelete={(index) => this.handleDelete(index)}
onClick={(index) => this.handleCountdown(index)}
onDoubleClick={(index) => this.handleDblClick(index)}
/>
</div>
)
}
}
ReactDOM.render(
<CountdownApp />,
document.getElementById('app')
)
I managed to find the answer to my own question!
setState worked as expected. The bug was due to <li> container that wrapped the event handler.
Clicking <li> causes it to call onClick event (which is managed by handleCountdown function in CountdownApp component) which causes it to setState.
As the delete button was wrapped in <li> container, clicking the delete button calls 2 event listeners - handleCountdown and handleDelete. handleCountdown is called twice in this case, once from clicking <li> to expand and the next call when the delete button is clicked.
There is a high chance that the last async setState dispatched from handleCountdown overwrites handleDelete's setState. Hence, the bug.
Here is changes: (I recoded everything again so the names might differ a little but the logic stays the same)
class Countdown extends React.Component {
render () {
return (
<li>
<div onClick={this.props.onClick} > // Add this div wrapper!
{this.props.title} - {this.props.days}, {this.props.color}
</div>
{this.props.toShow ?
<ButtonsGroup
onDelete={this.props.onDelete}
onEdit={this.props.onEdit}
/>
: null}
</li>
)
}
}
So the solution is to separate the clickable area and the buttons. I added a div wrapper over the text in <li> so whenever the text in <li> is clicked, the added <ul> will be out of onClick event handler area.
I want to display the selected checkbox items, for which I'm using material-ui checkbox.
Right now I'm only able to display the items with checkboxes, but I am not able to display the selected items.
I know it is easy but I'm new to reactjs and redux so finding it difficult to start.
Hoping for a help.
Thank you.
this.state = {
data: [apple, kiwi, banana, lime, orange, grape],
}}
handleCheck(x) {
this.state.checkedValues.push(x);
}
render(){
{this.state.data.map((x) =>
<Checkbox
label={x} key={x.toString()}
onCheck={() => this.handleCheck(x)}
checked=true
}/>
)}}
Modifying the answer by #BravoZulu by adding the event as the argument in onChange() function.(Also note that use onChange() instead of onCheck() for material-UI checkboxes as shown in the official documentation). Also, don't forget to bind the function in the constructor. I hope this helps the community. Below is the code.
class App extends Component {
constructor(props) {
this.handleCheck = this.handleCheck.bind(this);
super(props);
this.state = {
data: [apple, kiwi, banana, lime, orange, grape],
checkedValues: []
}
}
handleCheck(e,x) {
this.setState(state => ({
checkedValues: state.checkedValues.includes(x)
? state.checkedValues.filter(c => c !== x)
: [...state.checkedValues, x]
}));
}
render() {
return (<div>
{ this.state.data.map(x =>
<Checkbox
label={x} key={x.toString()}
onChange={e => this.handleCheck(e,x)}
checked={this.state.checkedValues.includes(x)}
/>
)}}
</div>)
}
}
In the handleCheck function, you are attempting to update your component state incorrectly. You need to use setState to make changes to state. In your example, state isn't getting updated so that is probably why you aren't seeing anything get selected. Also, gonna help clean up your example a bit:
class CheckboxList extends React.Component{
constructor() {
super();
this.state = {
data: ['apple', 'kiwi', 'banana', 'lime', 'orange', 'grape'],
checkedValues: []
}
}
handleCheck(index) {
this.setState({
checkedValues: this.state.checkedValues.concat([index])
});
console.log(this.state.checkedValues.concat([index]))
}
render(){
const checks = this.state.data.map( (item, index) => {
return (
<span key={item}>
<input type="checkbox"
value={item}
onChange={this.handleCheck.bind(this, index)} //Use .bind to pass params to functions
checked={this.state.checkedValues.some( selected_index => index === selected_index )}
/>
<label>{item}</label>
</span>)
});
return <div>{checks}</div>
}
}
Update:
Added working jsfiddle.
A bit late to the party but here's a solution using a functional component and hooks
import React, { useState } from 'react';
import Checkbox from '#material-ui/core/Checkbox';
const App = ({ data }) => {
const [checked, setChecked] = useState([]);
const handleCheck = (event) => {
const { value } = event.target;
setChecked(checked.includes(value) ? checked.filter(c => c !== value) : [...checked, value]);
};
return (
<div>
{data.map(({ value }) => (
<Checkbox onChange={e => handleCheck(e)} checked {checked.includes(value)} />
))}
</div>
);
};
export default App;
In React, you shouldn't push data directly to your state. Instead, use the setState function.
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [apple, kiwi, banana, lime, orange, grape],
checkedValues: []
}
}
handleCheck(x) {
this.setState(state => ({
checkedValues: state.checkedValues.includes(x)
? state.checkedValues.filter(c => c !== x)
: [...state.checkedValues, x]
}));
}
render() {
return (<div>
{ this.state.data.map(x =>
<Checkbox
label={x} key={x.toString()}
onCheck={() => this.handleCheck(x)}
checked={this.state.checkedValues.includes(x)}
/>
)}}
</div>)
}
}
I was also stuck on this issue for quite some time when I finally found a fix to this. It never works for a functional component which returns a check box. I made a separate class component and wrapped it in Redux Field component and it worked perfectly. I really never understood why it didn't work for the fucntional component as it what is shown in their official example.
I have written the code that worked for me. Hope it helps :)
class CheckBoxInput extends React.Component {
onCheckBoxSelectChange = (input) =>
{
input.onChange();
this.props.onSelectChange();
}
render() {
const { label, input,} = this.props;
let name = input.name;
return (
<div>
<InputLabel htmlFor={label} style={{paddingTop: '15px'}}> {label} </InputLabel>
<FormControl {...input} >
<Checkbox
name = {name}
label = {label}
color= "primary"
checked={input.value ? true : false}
onChange={() => this.onCheckBoxSelectChange(input)}
/>
</FormControl>
</div>
)
}
}
const CheckBox = (props) => <Field component={CheckBoxInput} {...props} />;
export default CheckBox;
And to use this checkbox component:
<CheckBox name="isCurrent" label="Current" onSelectChange = {this.toggleCurrentEmployerSelection} />
In case you are working with objects instead of simple data types, here is a working approache:
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [{id: 1, name:'banana'},
{id: 2, name:'kiwi'}],
checkedValues: []
}
}
handleCheck(element) {
const values = this.state.checkedValues.filter(e => e.id === element.id).length > 0
? this.state.checkedValues.splice( this.state.checkedValues.findIndex( e => e.id === element.id),1)
: this.state.checkedValues.push(element);
this.setState({
checkedValues: values
});
}
render() {
return (<div>
{ this.state.data.map(el =>
<Checkbox
checked={this.state.checkedValues.filter(e => e.id === el.id).length > 0}
onChange={this.handleCheck.bind(this, el)} //Use .bind to pass params to functions
value={el}
/>
)}}
</div>)
}
}
So basically what the function handleCheck does is it checks whether the selected object is in the checkedValues array, if that is the case then it deletes it (case uncheck), otherwise it adds it (case check), is i add the checked object to the checkedValues array.
in the Checkbox checked checks whether there is an object in the checkedValues array that is equal to the current loop object, (case checked/unchecked)
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()
}