I have a component I would like to uncheck from a postal.js subscription. Using my code below the checkbox is always checked.
I am storing the value in state and setting it in ComponentDidMount. Can anyone tell me how I can uncheck it once I receive the subscription:
UPDATED:
var SelectAll = React.createClass({
getInitialState: function() {
return {
checked:false
};
},
handler: function(e) {
var updatedContacts = [],
contacts = this.props.data.contacts,
topic = 'selectAll',
checked = false,
channel = 'contact';
contactChannel.publish({
channel: channel,
topic: topic,
data: {
selectAll: this.state.checked
}});
this.setState({checked: event.target.value});
},
render: function() {
return (
<div className="contact-selector">
<input type="checkbox" checked={this.state.checked}
onChange={this.handler} ref="checkAll" />
</div>
);
},
setUnChecked: function(){
this.setState({ selected: false });
},
componentDidMount: function() {
var self = this;
contactChannel.subscribe("deselectedContact", function(data) {
self.setUnChecked();
});
}
});
module.exports = SelectAll;
If you specify the checked attribute to a form field in ReactJS, you set it as controlled. This means, that simple pressing it by a user won't change it's state (or writing sth in case of text fields won't change the text). You need to set it yourself, for example in your handler() function.
P.S. The data flow inside of the component is imho a bit of a mess - according to what I've just wrote, you should use some this.state.XXX variable in the checked={XXX} attribute and, when ckbx's pressed, update it in the handler() function using `this.setState()'. This will trigger automatic rerendering of the component in the DOM (of course if the state changes).
EDIT
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
<script src="https://fb.me/react-0.13.3.js"></script>
<div id="container">
</div>
<script type="text/jsx;harmony=true">void function() {
'use strict';
var MyComponent = React.createClass({
getInitialState: function() {
return {
checked: false
};
},
handle: function(e) {
/*
Do your processing here, you might even prevent the checkbox from changing state - actually that is the main purpose of Facebook implementing the controlled/uncontrolled elements thing - to have full control of the input.
*/
var currentMinute = Math.floor(new Date().getTime() / 1000 / 60);
//The checkbox can be changed only in odd minutes
if(currentMinute % 2 == 1) {
this.setState({checked: e.target.checked});
}
},
render: function() {
return <input type="checkbox" checked={this.state.checked} onChange={this.handle} />;
}
});
React.render(<MyComponent />, document.getElementById('container'));
}()</script>
Something like this maybe ?
setChecked: function(isChecked) {
this.setState({
selected: isChecked
});
},
componentDidMount: function() {
var self = this; // <--- storing the reference to "this" so that it can be later used inside nested functions
contactChannel.subscribe("deselectedContact", function(data) {
self.setChecked(false); // <--- "this" would not refer to the react component hence you have to use self
});
}
You can use componentWillReceiveProps(), it is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
Related
I'm learning ReactJS and need to pass a variable inside the same component.
Here's an example
var DataBase = [
{
position: 1
},
{
position: 2
},
{
position: 3
},
{
position: 4
}
];
var Component = React.createClass({
getDefaultProps: function() {
var counter = 0;
},
componentDidMount: function() {
var dbPos = this.props.db[counter+1].position;
return dbPos;
},
render: function () {
return (
<div className="Component">
{this.dbPos}
</div>
);
}
});
ReactDOM.render(
<Component db={DataBase} />,
document.getElementById('main')
);
So, this obviously doesn't work. What I need is to pass var dbPos created in componentDidMount to the render (without any events like onClick). This will be time driven, like 10 seconds in each position with setTimeout().
Is this possible? How? Is there a better solution? I'll appreciate all your help.
That problem may regard state handling. There are multiple ways to handle the application's state in a React application, but I will assume that you are interested in keeping dbPos as part of the component's state (and that you may be mutating it in the future). To achieve this, simply use this.setState and this.state.
Before I show the example, I will state a few other mistakes in your code snippet:
getDefaultProps should return a hash object of the props, not declare them with var (that would make them scoped to the method rather than the component instance)
counter, as a prop, must be referred as this.props.counter. Note that counter is not part of this component's state, and can only change with a respective change of that prop in an upper level of the component tree.
With that in mind:
var Component = React.createClass({
getDefaultProps: function() {
return {counter: 0};
},
componentDidMount: function() {
var dbPos = this.props.db[this.props.counter+1].position;
this.setState({ // change the state of this component
dbPos: dbPos
})
},
render: function () {
return (
<div className="Component">
{this.state.dbPos}
</div>
);
}
});
If you do not want dbPos to mutate as part of the component's state, simply make a new method for retrieving the intended position. No mutable state will be involved here.
var Component = React.createClass({
getDefaultProps() {
return {counter: 0};
},
componentDidMount() {
// no longer needed
},
getPosition() {
return this.props.db[this.props.counter + 1].position;
},
render () {
return (
<div className="Component">
{this.getPosition()}
</div>
);
}
});
I am currently moving toward more of a TDD approach and want to get better at testing React components. One aspect of testing React components that I am struggling with is that of testing callbacks of a child component to a parent component.
What is an effective way of testing internal React component communication, such as callbacks to parent components?
The response to this question seems to offer a possible solution, though I didn't quite understand it (eg I'm not completely familiar with how to use function chaining in a Jasmine test.)
Thanks in advance for any tips and advice!
Example
(The following example uses Meteor, though I am not necessarily looking for Meteor-specific solutions.)
Repo with the complete example.
Let's say I have a component that accepts text input and passes it via props on submit:
SingleFieldSubmit = React.createClass({
propTypes: {
handleInput: React.PropTypes.func.isRequired
},
getDefaultProps() {
return {
inputValue: ""
};
},
getInitialState() {
return {
inputValue: this.props.inputValue
};
},
updateInputValue(e){
this.setState({inputValue: e.target.value});
},
handleSubmit(e) {
e.preventDefault();
this.handleInput();
},
handleInput(){
this.props.handleInput(this.state.inputValue.trim());
},
render() {
return (
<form className="single-field-submit" onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.inputValue}
onChange={this.updateInputValue}
/>
</form>
)
}
});
Here, I want to test if the component passes the user input on submit. My currently, somewhat clunky, solution is to create a mock parent component, with the component I want to test included as a child:
MockParentComponent = React.createClass({
getInitialState: function() {
return {
callbackValue: null
};
},
handleCallback: function(value) {
this.setState({callbackValue: value});
},
render: function() {
return (
<div className="container">
<SingleFieldSubmit handleInput={this.handleCallback} />
</div>
)
}
});
Then, my (Jasmine) test looks like this. The test passes. However, it seems like there should be a simpler way of doing this....
describe('SingleFieldSubmit Component', function () {
it('should, on submit, return the value input into the form', function () {
//SETUP
let mockUserInput = 'Test input';
let parentComponent = TestUtils.renderIntoDocument(
React.createElement(MockParentComponent)
);
let node = ReactDOM.findDOMNode(parentComponent);
let $node = $(node);
expect(parentComponent.state.callbackValue).toBe(null);
//TEST
Simulate.change($node.find('input')[0], { target: { value: mockUserInput } });
Simulate.submit($node.find('form')[0]);
expect(parentComponent.state.callbackValue).toBe(mockUserInput);
});
});
One method that doesn't require the parent component is to use jasmine spies.
describe('SingleFieldSubmit Component', function () {
it('should call handleInput prop with value of the input on submit', function () {
//SETUP
let callbackSpy = jasmine.createSpy('callbackSpy');
let mockUserInput = 'Test input';
let component = TestUtils.renderIntoDocument(<SingleFieldSubmit handleInput={callbackSpy} />);
let form = TestUtils.findRenderedDOMComponentWithTag(component, 'form');
let input = TestUtils.findRenderedDOMComponentWithTag(component, 'input')
//TEST
Simulate.change(imput, { target: { value: mockUserInput } });
Simulate.submit(form);
expect(callbackSpy).toHaveBeenCalledWith(mockUserInput);
expect(callbackSpy.calls.count()).toEqual(1);
});
});
In my React component I'm using the MediaMixin to apply classes based on media queries. Here's a simplified example:
R.createClass({
mixins: [MediaMixin],
render: function () {
var mediaquery = this.state.media;
return (
<Component responsive={mediaquery.small}>
<input value={this.state.formInput1} >
<input value={this.state.formInput2} >
<input value={this.state.formInput3} >
</Compontent>
)
}
});
In the above, {mediaquery.small} returns true or false depending on viewport size.
The component also has an initial state used to set some values for a form in the component.
getInitialState: function () {
return {
formInput1: '',
formInput2: '',
formInput3: null
};
},
When the form is submitted/saved, or the form is cancelled, in order to reset the form we use:
_cancel: function () {
this.setState(this.getInitialState());
}
This causes issues with the mixin however, as it removes the state property containing the mixin's mediaquery.
The solution I'm using now involves resetting the state properties related to the form manually, like:
_cancel: function () {
this.setState({
allowanceType: '',
allowanceAmount: '',
allowanceDocument: null
});
}
Question - How can I reset the state and keep the mixin state properties (without manually resetting each form state property)?
You can use this.
_cancel: function () {
var newState = this.getInitialState();
newState.media = this.state.media;
this.setState(newState);
}
Remember, that you should treat current state as immutable (just for info).
You could store the initial state of your component inside another function that getInitialState as it's shared with the mixin.
Like:
getInitialState: function() {
return this.componentInitialState();
},
componentInitialState: function() {
return {
allowanceType: '',
allowanceAmount: '',
allowanceDocument: null
};
},
cancel: function() {
this.setState(this.componentInitialState());
}
It seems that my component props are not updated the way I thought they ought to be.
var AdditionalInfoBlock = React.createClass({
getInitialState: function() {
return ({
comment: this.props.comment
});
},
onCommentChange: function(e) {
this.setState({comment: e.target.value});
this.props.onCommentChange(e);
},
render: function() {
return (
<ToolBlock>
<div className="container">
<form>
<textarea value={this.props.comment} onChange={this.onCommentChange} />
</form>
</div>
</ToolBlock>
);
}
};
var MainTool = React.createClass({
getInitialState: function () {
return {
comment: undefined
};
},
restart: function (e) {
e && e.preventDefault && e.preventDefault();
this.setState(this.getInitialState());
},
onCommentChange: function(e) {
this.setState({
comment: e.target.value
});
},
render: function() {
return (
<div>
<AdditionalInfoBlock comment={this.state.comment}
onCommentChange={this.onCommentChange} />
</div>
);
}
};
What I want this code to do is basically hold the comment's value until I post it and call restart - then it should reset the value in both AdditionalInfoBlock and MainTool. At the moment after restart is called when I console.log() the state of MainTool, the comment value is undefined. However, if I log AdditionalInfoBlock state and/or props, then the comment value is not reset in neither of those.
(PS. This is obviously a short version of the code, hopefully only incorporating the relevant bits. Although restart is not called in this excerpt, it doesn't mean that I have forgotten to call it at all :))
Since you're handling the same state on both components MainTool and AdditionalInfoBlock, finding value of the comment can get confusing. While you're listening for the comment change, you're setting the state on both components.
The changed state in your parent component MainTool is passing the props to the child AdditionalInfoBlock. getInitialState is invoked once before mounting (getInitialState documentation). Therefore, the passed on property is not handled by you child component on succeeding updates. By using componentWillReceiveProps, you will be able to handle the props sent by MainTool.
var AdditionalInfoBlock = React.createClass({
...
componentWillReceiveProps(nextProps) {
this.setState({comment: nextProps.comment});
},
Working Code: https://jsfiddle.net/ta8y1w1m/1/
Is this the correct way to handle the model change event -
a. The handleModelChange function is being passed as onModelChange prop to SubClass.
b. When the model change event triggers, for the re-render to occur, the handler from the SubComponent changes the state of the MainComponent.
var _SomeMixin={
componentWillMount: function() {
this.props.options.model.on("MODEL_CHANGED", this.props.onModelChange);
},
componentWillUnmount: function() {
this.props.options.model.off("MODEL_CHANGED", this.props.onModelChange);
},
/* more mixin functions */
}
var SubComponent = React.createClass({
mixins: [_SomeMixin],
render: function() {
return (
<div>
<!-- ... more elements .. >
</div>
);
}
});
var MainComponent = React.createClass({
getInitialState: function() {
return {MainComponentState: []};
},
handleModelChange: function() {
if (this.isMounted()) {
this.setState({MainClassState: this.props.options.model.toJSON()});
}
},
render: function() {
return (
<SubClass options={this.props.options} onModelChange={this.handleModelChange} />
);
}
});
This is one of possible ways to inform parent component that inner component has been changed. But this approach will lead you to a callbacks hell, that you will have to pass every time.
Better solution is to use some state management library like Moreartyjs