I am trying to toggle the colours of two buttons in ReactJS. I can set the active state property of the selected button OK, but I can't work out how to change the style of another button (calcY) based on my selection (of calcX).
The code is brittle but I am pretty new to react and any pointers on best practices would be appreciated. PS also I am using react-bootstrap for the Form and buttons.
const MyForm = React.createClass({
handleChange(event, attribute) {
let eventValue = event.target.value;
if (attribute === 'calcX'){
this.setState({active: true});
this.setState({bsStyle: 'info'});
let calcXBtn = ReactDOM.findDOMNode(this.refs.calcBtnGroup.refs.calcX);
calcXBtn.setState({bsStyle: 'default'});
}
...
}
render() {
return (
<Form onSubmit={this.handleSubmit} horizontal>
<FormGroup>
<ButtonGroup ref="calcBtnGroup">
<Button active className='btn btn-info' ref="calcX" onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
<Button className='btn btn-default' ref="calcY" onClick={(event) => this.handleChange(event, 'calcY')}>Calculate Y</Button>
</ButtonGroup>
...
);
}
});
module.exports = MyForm;
You can set the className or style based of an element (or subcomponent) on the state of your component. It's nice to use a ternary operator and ES6 template literals here.
<Button ref="calcX" className=`btn ${this.state.active ? 'btn-info' : 'btn-default'}` onClick={(event) => this.handleChange(event, 'calcX')}>Calculate X</Button>
What this does, is setting a className based on the state of your component. The <Button> component always has a btn className. If state.active is true, the class btn-info will be added. Otherwise btn-default will be added.
So the only thing you have to do now, is set the state in your handleChange method and the classNames will be rendered appropriately.
Edit: it's not really necessary to use refs here. It's almost never necessary to use refs. You want use React events (onChange, onSubmit, etc.) to set input values on the state, and render those values in the value in your inputs. These are called controlled components. You can read more about it in the official documentation: https://facebook.github.io/react/docs/forms.html
Related
function setTodoInfo(id) {
let todoInfo = todoInfoRef.current.value
if(todoInfo === "") return
todo.info = todoInfo
todoInfoRef.current.value = null
}
<>
<h1 className="delete-button"> hi, {todo.info} </h1>
<form>
<input type="text" ref={todoInfoRef}/>
</form>
<button onClick={closeInfo} className="delete-button" > Close </button>
<button onClick={setTodoInfo}> Set Info</button>
</>
When I click the set Info button its updating the info property on the todo, but it doesn't display it when you click, you have to close it and reopen it to see the updated info
react uses reference to be able to see it should or not rerender. using refs means the reference doesnt change...
as pointed, you should really see the docs on how to make it the "react way"
if you really need to make with references, then you could add some "render" function.
put in a useState a integer or something else, then call setState to change its value... that should force a render.
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => value + 1); // update the state to force
}
(inside functional component)
const forceUpdate = useForceUpdate();
call forceUpdate where needed
I am using a component that I cannot change directly, but I would like to extend.
import { Button } from '#external-library'
// Currently how the button component is being used
<Button click={() => doSomething()} />
// I would like to add a tabIndex to the button
<Button click={() => doSomething()} tabIndex={0} />
I cannot add an attribute because the component is not expecting a tabIndex. I cannot directly modify the Button component.
How can I extend the <Button /> component so I can add attributes like tabIndex, etc?
I was hoping something like the following would work:
export default class ExtendedButton extends Button { }
// except I'm dealing with functional components
You can't edit custom component implementation without changing its internals.
// You can't add tabIndex to internal button without changing its implementation
const Button = () => <button>Click</button>;
In such cases, you implement a wrapper with desired props:
const Component = () => {
return (
<div tabIndex={0}>
<Button />
</div>
);
};
If the component forwarding ref (also depends to which element it forwarded in the implementation), you can use its attributes:
// Assumption that Button component forwards ref
const Button = React.forwardRef((props,ref) => <button ref={ref}>Click</button>);
<Button ref={myRef}/>
// Usage
myRef.current.tabIndex = 0;
You can access the inner DOM button element using React refs(read here)
most likely the external-lib you use provide a ref prop for the Button component which you use to pass your own create ref
const buttonRef = useRef(null);
<Button ref={buttonRef}/>
Then you can use buttonRef.current to add tabIndex when your data is ready to be populated in like
useEffect( () => {
if(buttonRef && buttonRef.current){
buttonRef.current.tabIndex = 2;
}
}, [props.someProperty] );
So I have this form that updates the search in real time via storeUserSearch, which is why a value isn't set on input. I'm trying to implement an icon that deletes what's in the input field on click, and also runs the action storeUserSearch('').
The second onClick has no problems by itself. I'm simply trying to get the first onClick to work, and then have them both running at the same time so my click executes both.
<form>
<input
className="searchBox fas fa-search"
type="search"
placeholder="Search"
onChange={event => this.props.storeUserSearch(event.target.value) } />
<i className="fas fa-times-circle" onClick={event => event.target.parentElement.input} onClick={event => this.props.storeUserSearch('')}> </i>
</form>
Edit: Figured out how to reset the value via
onClick={event => event.target.parentElement.firstChild.value=''}
Just need to figure out how to combine the onClicks now.
why you're writing two onClick on same element? just pass a method on its click event and write all the logic there.
i.e:
<i className="fas fa-times-circle" onClick={this.handleIconClick}> </i>
// and in handleIconClick function write the logic.
handleIconClick = (e)=>{
e.target.parentElement.firstChild.value='';
this.props.storeUserSearch('');
}
You can't define two onClick props, one will overwrite the other. You can try something like
onClick={(event) => {event.target.parentElement.firstChild.value=''; this.props.storeUserSearch('')}}
note the semi-colon between the two individual statements
You can only have 1 onClick attribute per component, not multiple. I'm not sure exactly what storeUserSearch function is supposed to do since it's omitted from your code snippet, but based on what you've explained, I think you can achieve what you want to do similar to below:
class SearchForm extends React.Component {
constructor () {
super();
this.state = {
inputValue: ''
};
}
render() {
return (
<div>
<input
type="text"
value={ this.state.inputValue }
onChange={
e => {
this.setState({inputValue: e.target.value})
}
}
/>
<input
type="button"
value="Clear input"
onClick={
() => {
this.setState({inputValue: ''})
}
}/>
<span style={{ display: 'block' }}>Current textbox state: <span style={{ color: 'blue' }}>{this.state.inputValue}</span></span>
</div>
);
}
}
ReactDOM.render(<SearchForm></SearchForm>, document.querySelector('#app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Basically we create a component that is made up of a 2 input elements (1 text and 1 button). The approach I took was to simply set the value attribute equal to our component's state.inputValue, which is updated each time a change event occurs on it.
We just hardcode the state.inputValue property to '' when the clear button is pressed.
I am new to React. I have a few buttons in a button group:
<div className="btn-group">
<button className="btn btn-mini btn-default" onClick={() => this.changeDataType("type1")} >Button1</button>
<button className="btn btn-mini btn-default" onClick={() => this.changeDataType("type2")} >Button2</button>
<button className="btn btn-mini btn-default" onClick={() => this.changeDataType("type3")} >Button3</button>
</div>
Whenever the user clicks on one of the buttons, this button should become the active, selected one. I found out that I need to add the CSS class active to the corresponding button, but I am not sure how to implement this.
I thought about this for a bit. I have a changeDataType function connected to my buttons, in which I do some other stuff. Would I then, in there, somehow manipulate the CSS?
So I guess my questions are first, how to target the button I need to target, and second, how I could change that button's CSS with React.
In react when state changes react re-renders. In your case if you want to change the way something looks then you probably want to force another render. What you can do is have the className be part of state and then update the state when you want, causing the component to re-render with the new className. For example:
constructor() {
super();
this.state = {
className: 'btn'
}
}
render () {
return (
<Button className={this.state.className}>Click me</Button>
)
}
Since the className is bound to state updating state will cause the button to render again with the new className. This can be done like this:
updateClass() {
let className = this.state.className;
className = className + ' ' + 'btn-primary'
this.setState({className})
}
This is an example of the function you can call on the click of a button and update the className for the button.
There's a nice utility you can use for classname logic + joining classNames together
https://github.com/JedWatson/classnames
Based on setting React state for active you could do something like the following. You can get as complex as you need to with the logic. If the logic result is falsy, that key won't be included in the output.
var classNames = require('classnames');
var Button = React.createClass({
// ...
render () {
var btnClass = classNames({
btn: true,
'btn-active': this.state.isActive
});
return <button className={btnClass}>{this.props.label}</button>;
}
});
Here how I did this:
//ChatRoomPage component
function ChatRoomPage() {
const [showActionDropdown, setShowActionDropdown] = useState('hide');
function showActionDropdownHandler(){
console.log("clicked")
if(showActionDropdown=='hide')
setShowActionDropdown('show')
else
setShowActionDropdown('hide')
}
return (
<div>
<button onClick={ () => showActionDropdownHandler() } className="btn " type="button">Actions</button>
<div className={`action_menu ${showActionDropdown}`}>
...
</div>
</div>
);
}
export default ChatRoomPage;
I'm using ReactJS and ES2015
I pass a button via props down into a child component.
I can't see how to get data from the child into the onClick function of the button
Can anyone suggest what I need to do to get data from the child component into the onCLick function?
doDeleteItem = (blah) => {
console.log('the item to delete is: ', blah);
};
deleteButton = (
<button
className="btn btn-expand btn-stroke btn-success mr-5"
type="primary"
onClick={this.doDeleteItem}Delete item
</button>
)
render() {
return (
<TableContainer
deleteButton={this.deleteButton}
doDeleteItem={this.doDeleteItem}
/>
);
UPDATE: the comments say it's a bit unclear.
Here's the context:
The TableContainer component displays rows of data.
I push Button components down into the TableContainer via props.
The TableContainer renders the buttons.
I also push a function down for the Button to call in its onClick event.
The idea being that the user selects rows in the table, they push the button (such as delete for example) and the button runs its onClick function which deletes the selected rows.
The problem is that I can't see how to get the data that defines the selected rows into the onClick function.
It appears that the "scope" of the onClick function is actually the parent component, not the TableContainer component, so it cannot see the variables the define which data rows the user has chosen to delete.
Is that more clear? Let me know if not. thanks
Try to define doDeleteItem, deleteButton as methods instead of properties, and then call deleteButton in child and pass arguments what do you need
doDeleteItem(rows) {
console.log('the item to delete is: ', rows)
};
deleteButton(rows) {
return <button
className="btn btn-expand btn-stroke btn-success mr-5"
type="primary"
onClick={ () => this.doDeleteItem(rows) }
>
Delete item
</button>
}
render() {
return (
<TableContainer
deleteButton={ this.deleteButton }
doDeleteItem={this.doDeleteItem}
/>
);
}
Example
You should just pass down the parent function to the child as a property and separate the button component from the parent completely. You can include this button component in the child component (although it would be best to separate this component from child also) and insert the parent function there. If you want to pass parameters, you need to bind the function.
Parent:
doDeleteItem = (blah) => {
console.log('the item to delete is: ', blah);
};
render() {
const locale = this.props.app.locale;
return (
<TableContainer
onButtonClick={this.doDeleteItem.bind(this, blah)}
doDeleteTableItem={this.doDeleteTableItem}
/>
);
Child (TableContainer):
render() {
var deleteButton = (
<button
onClick={this.props.onButtonClick}
</button>
)
return (
// render TableContainer with deleteButton ..
);
As #WitVault mentioned in his comment you could pass some reference from the onClick in deleteButton so that the table knows which one to delete.
deleteButton = (
<button
className="btn btn-expand btn-stroke btn-success mr-5"
type="primary"
onClick={() => this.doDeleteItem(referenceToMeSoTableKnows)}>
Delete item
</button>
)
In your code deleteButton is getting called for just returing jsx. So you must be using it inside render method of TableContainer to render buttons.
and since here
<TableContainer
deleteButton={ this.deleteButton() }
doDeleteItem={this.doDeleteItem}
/>
doDeleteItem is passed as props inside TableContainer. you can access it via this.props inside TableContainer. Note this scope should be referring to scope of TableContainer so that you can access its props.
deleteButton = (
<button
className="btn btn-expand btn-stroke btn-success mr-5"
type="primary"
onClick={this.props.doDeleteItem.bind(this,item)}
</button>
)
I hope this should work.