I have a stateful Key component that represents a Key in a Keyboard like so:
import React from 'react';
class Key extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
customClass: props.customClass,
value: props.value,
onAction: props.onAction,
active: false
};
this.handleAction = this.handleAction.bind(this);
this.resetActiveState = this.resetActiveState.bind(this);
}
handleAction(e) {
e.preventDefault();
this.setState ({
active: true
});
this.state.onAction(this.state.value);
//remove key pressed effect after 150 ms by resetting active state
_.delay(() => this.resetActiveState() , 150);
}
resetActiveState() {
this.setState ({
active: false
});
}
render() {
//conditionalProps is empty when active is false.
let conditionalProps = {};
let className = `default-btn ${this.state.customClass}`;
let displayValue = this.state.value;
//enable css attribute selector
if (this.state.active){
conditionalProps['data-active'] = '';
}
return (
<button id={this.state.id} key={this.state.id} className={className}
data-value={this.state.value} {...conditionalProps} onTouchStart={this.handleAction}>
{displayValue}
</button>
);
}
}
Key.defaultProps = {
customClass: '',
onAction: (val) => {}
};
export default Key;
onTouchStart is used to detect a touch event.
onTouchStart handler changes active state to true.
Component re-renders with the appropriate css to give key clicked
effect.
After 150ms, active state is set to false using resetActiveState().
Component re-renders without the key clicked effect css.
conditionalProps attribute is used to conditionally add css styles (using css attribute selector) to achieve 'key pressed' look in the rendered component.
This works as expected but I was wondering if it would be possible to refactor the component so I can extract the logic to maybe a parent component which I can then extend using the Key component.
This would be a perfect use case for a Higher Order Component.
This will allow you to abstract much of the functionality and pass it down to stateless components as props.
The React official docs do a great job of explaining how to use HOCs.
Related
Situation: I have a class with its own methods. This class is instantiated in a React component.
What I'm needing: During one of the methods in the class, it changes the value of an input (this.$el) using .val(), but I'm listening to changes to this input in the React.component via onChange. I need to pass the value I'm using to set the value of the input (via this.$el.val(value)) to the React component to change its state.
What I've tried: I've tried chaining .change() and trigger('change') to the val(value), but it doesn't have any affect.
So, I need to be able to access the value I'm using in .val(value) in my React component WHEN it is set in the class method. I thought about using a method and calling that method on componentWillUpdate, but the component doesn't update since setting the input value via val() doesn't trigger a change.
Any ideas?
Component code:
// Create a ref to manage blur/focus state on the search input
this.inputRef = React.createRef()
// Setup initial state
this.state = {
supersearchResults: [],
value: this.props.initialValue || ''
}
this.onInputChange = this.onInputChange.bind(this)
tag('input', {
autoComplete: 'off',
className: blockElement('search-input'),
onChange: this.onInputChange,
placeholder: 'Find people, collections and pages',
ref: this.inputRef,
type: 'text',
value: this.state.value
})
Class code:
this = class
this.$el = input
// What is happening:
// A user types in an input, suggestions display in a list, when you
// select a suggestion, it calls the below to change the input value
this.$el.val(complete)
this.$el.blur()
this.hide()
If I understand correctly, you want to be able to access the value of a html field. Then please take into consideration the following considerations. Use controlled inputs such that the
class ReactComponent extends...
constuctor (props) {
super();
this.state = { fieldValue: props.fieldValue || '' };
}
onFieldChange = (event) => {
this.setState('fieldValue': event.currentTarget.value)
}
render () {
return (
<div>
<input type="text"
value={this.state.fieldValue}
onChange={this.onFieldChange}
>
<div>
)
}
}
Now having this code, in case you need to use some external class to call some code, simply put it correctly in the lifecycle. But in order to reference the value use the components state. And in case you want to programmatically want to change the value, do the same update the value in the state. If I missed something let me know in the comments.
You need to keep the state in your class component. consider the following
class TextExample extends Component{
constructor(){
super(props);
this.state ={
username: null
}
this._handleChange = this._handleChange.bind(this);
}
_handleChange(e){
const { name, value } = e.target;
this.setState({ username: value}) // for single TextField
// if you want to reuse this _handleChange function for all the TextFields then you need to use the below commented code which updates state of current TextField
//this.setState({ [name]: value }) // it is similar like the JSON bracket notation
}
render(){
return(
<div>
<TextField
id="username"
label="Username"
name="username"
value={this.state.username}
onChange={this._handleChange} // it will call the _handleChange function on every keypress inside the TextField.
/>
</div>
)
}
}
I have a dropdown as is shown in the following image:
When I click the folder icon it opens and closes because showingProjectSelector property in the state that is set to false.
constructor (props) {
super(props)
const { organization, owner, ownerAvatar } = props
this.state = {
owner,
ownerAvatar,
showingProjectSelector: false
}
}
When I click the icon, it opens and closes properly.
<i
onClick={() => this.setState({ showingProjectSelector: !this.state.showingProjectSelector })}
className='fa fa-folder-open'>
</i>
But what I'm trying to do is to close the dropdown when I click outside it. How can I do this without using any library?
This is the entire component: https://jsbin.com/cunakejufa/edit?js,output
You could try leveraging onBlur:
<i onClick={...} onBlur={() => this.setState({showingProjectSelector: false})}/>
I faced same issue with you. Solved after reading this:
Detect click outside React component
Please try:
You should use a High Order Component to wrap the component that you would like to listen for clicks outside it.
This component example has only one prop: "onClickedOutside" that receives a function.
ClickedOutside.js
import React, { Component } from "react";
export default class ClickedOutside extends Component {
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
handleClickOutside = event => {
// IF exists the Ref of the wrapped component AND his dom children doesnt have the clicked component
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
// A props callback for the ClikedClickedOutside
this.props.onClickedOutside();
}
};
render() {
// In this piece of code I'm trying to get to the first not functional component
// Because it wouldn't work if use a functional component (like <Fade/> from react-reveal)
let firstNotFunctionalComponent = this.props.children;
while (typeof firstNotFunctionalComponent.type === "function") {
firstNotFunctionalComponent = firstNotFunctionalComponent.props.children;
}
// Here I'm cloning the element because I have to pass a new prop, the "reference"
const children = React.cloneElement(firstNotFunctionalComponent, {
ref: node => {
this.wrapperRef = node;
},
// Keeping all the old props with the new element
...firstNotFunctionalComponent.props
});
return <React.Fragment>{children}</React.Fragment>;
}
}
If you want to use a tiny component (466 Byte gzipped) that already exists for this functionality then you can check out this library react-outclick.
The good thing about the library is that it also lets you detect clicks outside of a component and inside of another. It also supports detecting other types of events.
Using the library you can have something like this inside your component.
import OnOutsiceClick from 'react-outclick';
class MyComp extends Component {
render() {
return (
<OnOutsiceClick
onOutsideClick={() => this.setState({showingProjectSelector: false})}>
<Dropdown />
</OnOutsiceClick>
);
}
}
Wrapper component - i.e. the one that wrapps all other components
create onClick event that runs a function handleClick.
handleClick function checks ID of the clicked event.
When ID matches it does something, otherwise it does something else.
const handleClick = (e) => {
if(e.target.id === 'selectTypeDropDown'){
setShowDropDown(true)
} else {
setShowDropDown(false);
}
}
So I have a dropdown menu that appears ONLY when you click on the dropdown menu, otherwise it hides it.
I'm writing an application in React and I'm unit testing this with Jest and Enzyme.
I have a very simple component that represents an input field, which contains the following code:
// 'Container' component definition.
class Container extends React.Component<containerProps, containerState> {
static defaultProps = { };
state = {
hasValue: false
};
constructor(props: containerProps) {
super(props);
// Bind all the event handlers for the component.
(this: any).onChange = this.onChange.bind(this);
}
onChange(event: MouseEvent) : void {
this.setState(
{
hasValue: (event.target: window.HTMLInputElement).value !== ''
}
);
// Prevent a default browser event from happening.
event.preventDefault();
}
createComponentProps(): componentProps {
return {
cssClass: this.createComponentCssClass()
};
}
// Create the CSS class to pass to the component.
createComponentCssClass(): string {
let className = '';
if (this.state.hasValue) { className = className !== '' ? className + 'value' : 'value'; }
if (this.props.errorMessage && this.props.errorMessage !== '') {
className = className !== '' ? className + ' error' : 'error';
}
// Return the CSS class to apply to the component.
return className;
}
// Renders the 'Container' component.
render(): React$Element<any> {
return (
<Component {...this.createComponentProps()} />
);
}
}
So, it's a fairly simple component.
Now, when the contents of the input field are changed, a state change occurs which forces a different CSS class to be applied to the component.
I can confirm that this is working since it's working in the browser as intended.
Now, I'm writing a unit test to verify that the className value is passed to the component using the following code:
it('Passes down the \'cssClass\' property to the \'Input Field\' Component.', () => {
// Act.
const wrapper = mount(
<InputFieldContainer primaryThemeColor="TEAL" accentThemeColor="PINK" useAccentColor={true} />
);
wrapper.find('input').simulate('change', { target: { value: 'value' }});
wrapper.update();
// Assert.
expect(wrapper.find(InputFieldComponent).props().cssClass).toEqual('value');
});
Thus, I'm rendering the component, I simulate a change event, and I check the CSS class property of the component, however, it's an empty string. It seems it didn't update regarding the state change (but only in unit tests).
Reading the state in Jest, using console.log(wrapper.state()) gives me a JSON object saying hasValue: true, so the state is updated, but even after calling wrapper.update(), the CSS class does not seem to be passed.
What am I missing here?
Kind regards
This seems to be an issue with Enzyme (https://github.com/airbnb/enzyme/issues/1153).
After updating the wrapper and your component should be in sync.
I'm using the npm package react-sortablejs. In my component I want to set the disabled option dynamically. Right now my component looks like this:
class Example extends Component {
constructor(props) {
super(props)
this.state = { }
}
render() {
const { disabled } = this.props // <-- Boolean value as property
return (
<List>
<Sortable
options={{
handle : '.sortable-handle',
draggable: '.sortable-item',
disabled : disabled // <-- Use the bool value
}}
>
<Items>
</Sortable>
</List>
)
}
}
The disabled value is stored in the DB. If that value changes, the sortable should be disabled/enabled. But in this way it doesn't work.
The package is based on RubaXa/Sortable. There I can set the disabled option like this:
var sortable = Sortable.create(list);
document.getElementById("switcher").onclick = function () {
var state = sortable.option("disabled"); // get
sortable.option("disabled", !state); // set
};
But how do I do that in a dynamic way in a react component?
Update
In the way I did it, the disabled option is just set initially on rendering the component. If the value changes after rendering, the option is not changed, so if the value changes from true to false, the disabled-option still keeps true.
i think that you have to bind the whole "option" variable.
judging from the lack of maintainer in react-sortablejs, it does not really bind to nested value yet.
try to put the options on state.
constructor(props) {
super(props);
this.state = {
option : {
handle : '.sortable-handle',
draggable: '.sortable-item',
disabled : true
}
}
}
bind the state's option
<Sortable options={this.state.option}>
<Items>
</Sortable>
And on ComponentDidUpdate() ,do this:
componentDidUpdate(prevProps, prevState) {
if(prevProps.disabled !== this.props.disabled){
this.setState({option : Object.assign({}, this.state.option, {disabled: this.props.disabled})})
}
}
my guts telling me that the component does not recognized the disabled update due to nested props. the whole option must be updated, so that it recognize the change.
OR>
you can pass the options as props instead.
and do the update on parent's component, each time the disabled change.
tl;dr React refuses to honor checked={checkThisOption} on inputs, even though it honors data-ischecked={checkThisOption} perfectly on the same set of inputs.
I haven't made this work on jsfiddle, but I have reproduced the issue using this code.
the long version
I've got a simple ReactJS component that presents a list of radio buttons to the user. The user is supposed to be able to pick a radio and then push a button to confirm their choice.
Here's the component def (note: I'm using ES6 & webpack):
import React from 'react';
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: null // tracks currently-selected choice, by its value
};
}
onClickOptionRadio = (event) => {
this.setState({
currentValue: String(event.currentTarget.value)
});
}
onConfirm = (event) => {
if(!this.props.onChange) return;
this.props.onChange(this.state.currentValue);
};
render() {
let choices = this.props.choices;
let currentValue = this.state.currentValue;
return (
<div className="Widget">
<ol className="choices">
{
choices.map((choice, i) => {
// decide whether to mark radio as checked:
// - if no current choice, check first radios
// - otherwise, check radio matching current choice
let noCurrentChoice = (currentValue === null);
let drawingFirstChoice = (i === 0);
let thisChoiceIsSelected = (String(choice.value) === currentValue);
let checkThisOption = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);
return (
<li key={i}>
<input type="radio" name="choices"
value={choice.value}
onChange={this.onClickOptionRadio}
checked={checkThisOption?'checked':''}
data-ischecked={checkThisOption}
/>
<label>{choice.label}</label>
{' '}
{checkThisOption ? 'CHECKED' : ''}
</li>
);
})
}
</ol>
<button onClick={this.onConfirm}>Confirm choice</button>
</div>
);
}
}
export default Widget;
Here's the owning component:
import React from 'react';
import Widget from 'components/widget';
class Owner extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let choices = [
{ value: 10, label: 'First' },
{ value: 20, label: 'Second' },
{ value: 30, label: 'Third' }
];
return (
<div className="Owner">
<Widget
choices={choices}
/>
</div>
);
}
}
export default Owner;
Here's a gif of it in action:
Note several things from the video:
the logic clearly works for checking the first radio on initial render
the other radios don't become checked when the user clicks on them
however, the logic clearly works for identifying which item is selected, as indicated by margin-right: 2rem on the radio that ought to be checked
the text indicating which option has been chosen is accurate throughout
when I click a radio, the componentWillUpdate method fires only for the Widget itself; none of its ancestors update
I think this demo proves that this isn't a case of the Widget instance being replaced by a different instance whose state is empty. The fact that the current selection is accurately reflected by a data- attr on the input, as well as plain text, shows that the state is persisting as desired. I am certain this unwanted behavior is by design, and I want to know how to work around the bizarre, exceptional logic that React applies to the form-related properties of controlled inputs.
Why do I think the current behavior is wrong? I don't want the owning component to know about each radio click -- the owner should bind to Widget's onChange method to be notified once a final choice is made.
This is a simplified example. The real component is more complicated, but the principle is the same: just as a date-picking component may have lots of internal state that the owning component is unaware of (like what time scale to show, which year, month, or week to display, etc.), so too does this component have some interesting internal state that owning components have no business managing.
As far as I can tell, I've done this exactly correctly. The component publishes its important state updates via onChange(event, newValue), which owning components should bind to. I think it's quite clear that React is deciding to not update the checked attr on these inputs, even though it's clearly capable of updating other attrs on the same elements in response to the same user actions.
Note that the owner isn't currently listening for the onChange, but that shouldn't matter: the child component should be able to manage its own internal state even when the owner isn't listening. And I reject the assertion that the radio state can't be accurate simply because Owner isn't providing a currentValue via props: Widget is plainly managing and rendering its state without that prop. React must be doing something special to prevent checked from being handled according to the rules that apply to every other element and attribute. This is an exception, and I think it's a bad one.
Finally, note that this problem only seems to occur when this component is beneath a certain comp-tree depth. When it is the only component in a Flux "page" or a Redux "container," it works great. When it's nested more deeply, it fails as I've described. I haven't yet worked out a concise way of showing that.
Any advice is appreciated. As far as I can tell, here React is violating its own stated rules, and I expect this behavior to frustrate building other stateful components that are built around vanilla inputs.
Edit: I corrected the generated names for the radios, and updated the demo to reflect it. Apologies to anyone who started chasing that stuff down.
I've edited it to not use class properties, and are not able to reproduce:
Code:
class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: null // tracks currently-selected choice, by its value
};
this.onClickOptionRadio = this.onClickOptionRadio.bind(this)
this.onConfirm = this.onConfirm.bind(this)
}
onClickOptionRadio (event) {
this.setState({
currentValue: String(event.currentTarget.value)
});
}
onConfirm (event) {
if(!this.props.onChange) return;
this.props.onChange(this.state.currentValue);
};
render() {
let choices = this.props.choices;
let currentValue = this.state.currentValue;
return (
<div className="Widget">
<ol className="choices">
{
choices.map((choice, i) => {
// decide whether to mark radio as checked:
// - if no current choice, check first radios
// - otherwise, check radio matching current choice
let noCurrentChoice = (currentValue === null);
let drawingFirstChoice = (i === 0);
let thisChoiceIsSelected = (String(choice.value) === currentValue);
let checkThisOption = thisChoiceIsSelected || (noCurrentChoice && drawingFirstChoice);
return (
<li key={i}>
<input type="radio" name="choices"
value={choice.value}
onChange={this.onClickOptionRadio}
checked={checkThisOption?'checked':''}
data-ischecked={checkThisOption}
/>
<label>{choice.label}</label>
{' '}
{checkThisOption ? 'CHECKED' : ''}
</li>
);
})
}
</ol>
<button onClick={this.onConfirm}>Confirm choice</button>
</div>
);
}
}
class Owner extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let choices = [
{ value: 10, label: 'First' },
{ value: 20, label: 'Second' },
{ value: 30, label: 'Third' }
];
return (
<div className="Owner">
<Widget
choices={choices}
/>
</div>
);
}
}
ReactDOM.render(
<Owner />,
document.getElementById('container')
);
My browser is Chrome 47. Here is the jsfiddle.