How to unit test forms with Sinon and Chai - unit-testing

I am using Chai, Sinon, and Mocha to test.
I am using Redux-Forms, along with ReactJS.
I want to test what happens after I click submit on a Forgot Password page.
Here's my code so far:
react file:
propTypes: {
fields: React.PropTypes.object.isRequired,
message: React.PropTypes.string.isRequired,
handleSubmit: React.PropTypes.func.isRequired,
},
renderForm: function() {
const { fields: {email}, handleSubmit, isFetching } = this.props;
const iconClass = "fa fa-star-o";
return(
<form form="forgotPassword" name="forgotPassword" className="stack-input">
<ReduxFormTextInput
{...email}
floatingLabelText="Email Address" />
<div className="clearfix" />
<div className="form-footer">
<ButtonSpinner spinner={isFetching} onClick={handleSubmit} ripple={true} raised={true} primary={true} >
<i className={iconClass} />Send
</ButtonSpinner>
</div>
</form>
);
},
renderMessage: function() {
return(
<div>
<i className="fa fa-exclamation-circle" style={{marginRight: '4px'}}/>
If your email is in the system, then it will be sent.
</div>
);
},
//Checks if user has submitted email.
emailSubmit: function(){
var locus = this, props = locus.props;
if(props.message === ''){
return null;
}
else if(props.message === 'SENT'){
return true;
}
return null;
},
render(){
var locus = this, props= locus.props;
return(
<div className="page-wrap">
<div className="page-column">
<h2>Forgot Your Password</h2>
{this.emailSubmit() ? this.renderMessage(): this.renderForm()}
</div>
</div>
);
}
unittest file:
describe('ForgotPasswordForm', () => {
const component = setup({
fields: {
email: {
onChange: spy(),
onBlur: spy(),
onFocus: spy()
}
}, // React.PropTypes.object.isRequired,
message: '', // React.PropTypes.string.isRequired,
handleSubmit: spy() // React.PropTypes.func.isRequired,
//onClearMessage:spy() //, React.PropTypes.func.isRequired
}),
domRoot = TestUtils.findRenderedDOMComponentWithClass(component, 'page-wrap'),
title = TestUtils.findRenderedDOMComponentWithTag(component, 'h2'),
submitButton = TestUtils.findRenderedDOMComponentWithClass(component, 'material-D-button'),
form = TestUtils.findRenderedDOMComponentWithTag(component, 'form'),
inputs = TestUtils.scryRenderedDOMComponentsWithTag(component, 'input'),
emailInput = inputs[0];
This test keeps failing, despite multiple attempts. I am not experienced with Spy(), so I'm not sure if I am suppose to be using calledWith.
it ('should display "If your email is in the system, then it will be sent." on submit', () => {
TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});
TestUtils.Simulate.click(submitButton);
expect(domColumn.text).to.equal("Forgot Your Password");
});
This is the response I get.
+"If your email is in the system, then it will be sent."
- -"Forgot Your PasswordSend"
I used innerHTML to get a sense of what's being populated after the click, and I don't think the click is even registering.
When I try to do TestUtils.Simulate.change(emailInput, {target: {value: 'test#email.com'}});, it doesn't work. I have to populate the value of the email in the component.

You should be assigning your handleSubmit spy to a constant so you can at least be able to check whether it's getting called. (Probably the same for the other spies).
const handleSubmitSpy = spy()
const component = setup({
...
handleSubmit: handleSubmitSpy
Now you can check expect(handleSubmitSpy).toHaveBeenCalled().

Related

Getting credit card brand and show error message when using hosted fields in Braintree

I am trying to create payment page using braintree's hosted fields.
I have created sandbox account.
But i am not getting additional details like Card brand, error message like Drop in UI.
How to get those functionalities using Hosted fields.
import React from 'react';
var braintree = require('braintree-web');
class BillingComponent extends React.Component {
constructor(props) {
super(props);
this.clientDidCreate = this.clientDidCreate.bind(this);
this.hostedFieldsDidCreate = this.hostedFieldsDidCreate.bind(this);
this.submitHandler = this.submitHandler.bind(this);
this.showPaymentPage = this.showPaymentPage.bind(this);
this.state = {
hostedFields: null,
errorOccurred: false,
};
}
componentDidCatch(error, info) {
this.setState({errorOccurred: true});
}
componentDidMount() {
this.showPaymentPage();
}
showPaymentPage() {
braintree.client.create({
authorization: 'sandbox_xxxxx_xxxxxxx'
}, this.clientDidCreate);
}
clientDidCreate(err, client) {
braintree.hostedFields.create({
onFieldEvent: function (event) {console.log(JSON.stringify(event))},
client: client,
styles: {
'input': {
'font-size': '16pt',
'color': '#020202'
},
'.number': {
'font-family': 'monospace'
},
'.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
'card-brand-id': true,
supportedCardBrands: 'visa'
},
cvv: {
selector: '#cvv',
type: 'password'
},
expirationDate: {
selector: '#expiration-date',
prefill: "12/21"
}
}
}, this.hostedFieldsDidCreate);
}
hostedFieldsDidCreate(err, hostedFields) {
let submitBtn = document.getElementById('my-submit');
this.setState({hostedFields: hostedFields});
submitBtn.addEventListener('click', this.submitHandler);
submitBtn.removeAttribute('disabled');
}
submitHandler(event) {
let submitBtn = document.getElementById('my-submit');
event.preventDefault();
submitBtn.setAttribute('disabled', 'disabled');
this.state.hostedFields.tokenize(
function (err, payload) {
if (err) {
submitBtn.removeAttribute('disabled');
console.error(err);
}
else {
let form = document.getElementById('my-sample-form');
form['payment_method_nonce'].value = payload.nonce;
alert(payload.nonce);
// form.submit();
}
});
}
render() {
return (
<div className="user-prelogin">
<div className="row gutter-reset">
<div className="col">
<div className="prelogin-container">
<form action="/" id="my-sample-form">
<input type="hidden" name="payment_method_nonce"/>
<label htmlFor="card-number">Card Number</label>
<div className="form-control" id="card-number"></div>
<label htmlFor="cvv">CVV</label>
<div className="form-control" id="cvv"></div>
<label htmlFor="expiration-date">Expiration Date</label>
<div className="form-control" id="expiration-date"></div>
<input id="my-submit" type="submit" value="Pay" disabled/>
</form>
</div>
</div>
</div>
</div>
);
}
}
export default BillingComponent;
I am able to get basic functionalities like getting nonce from card details. But i am unable to display card brand image/error message in the page as we show in Drop in UI.
How to show card brand image and error message using hosted fields?
Page created using Hosted fields:
Page created Drop in UI - Which shows error message
Page created Drop in UI - Which shows card brand
Though we do not get exact UI like Drop in UI, we can get the card type and display it ourselves by using listeners on cardTypeChange.
hostedFieldsDidCreate(err, hostedFields) {
this.setState({hostedFields: hostedFields});
if (hostedFields !== undefined) {
hostedFields.on('cardTypeChange', this.cardTypeProcessor);
hostedFields.on('validityChange', this.cardValidator);
}
this.setState({load: false});
}
cardTypeProcessor(event) {
if (event.cards.length === 1) {
const cardType = event.cards[0].type;
this.setState({cardType: cardType});
} else {
this.setState({cardType: null});
}
}
cardValidator(event) {
const fieldName = event.emittedBy;
let field = event.fields[fieldName];
let validCard = this.state.validCard;
// Remove any previously applied error or warning classes
$(field.container).removeClass('is-valid');
$(field.container).removeClass('is-invalid');
if (field.isValid) {
validCard[fieldName] = true;
$(field.container).addClass('is-valid');
} else if (field.isPotentiallyValid) {
// skip adding classes if the field is
// not valid, but is potentially valid
} else {
$(field.container).addClass('is-invalid');
validCard[fieldName] = false;
}
this.setState({validCard: validCard});
}
Got the following response from braintree support team.
Hosted fields styling can be found in our developer documentation. Regarding the logos, you can download them from the card types official websites -
Mastercard
Visa
AMEX
Discover
JCB
Or online from other vendors.
Note: Drop-In UI will automatically fetch the brand logos and provide validation errors unlike hosted fields as it is less customizable.

ReactJs: Passing a prop and using it within a map()

I'm trying to take a user inputted code and compare it to code within my database. Right now I can bring the code and display it outside the map function but when I try to add it, it doesn't work. here is my database:
[
{
"dwelling_code": "ABC-XYZ",
"dwelling_name": "Neves Abode",
"has_superAdmin": true,
"room": []
}
This is the parent component:
class Dwel2 extends Component {
state = {
house: [],
selectedMovie: null,
data: "ABC-XYZ"
}
componentDidMount() {
fetch('Removed for question', {
method: 'GET',
headers: {
}
}).then(resp => resp.json())
.then(resp => this.setState({ house: resp }))
.catch(error => console.log(error))
}
houseClicked = h => {
console.log(h)
}
render() {
return <div>
<EnterCode dataFromParent={this.state.data}
house={this.state.house}
houseClicked={this.house} />
</div>
}
}
This is the child component:
function EnterCode(props) {
return (
<div>
<div>
*THIS BIT DISPLAYS THE CODE*{props.dataFromParent}
</div>
{props.house.map(house => {
var test = house.dwelling_name
var code = house.dwelling_code
if (code === {props.dataFromParent}) {
test = "Test"
}
return (
<React.Fragment>
<div>{test}</div>
</React.Fragment>
)
})}
</div>
)
}
I just want to compare the code in the database to the code defined in the parent component. Here is the error that's coming up this is in the child component.
Line 17:31: 'dataFromParent' is not defined no-undef
You made a tiny mistake in the if statement. You put the props.dataFromParent in brackets, which in the context of JSX would be required, but in the context of JS means creating an object, which is clearly wrong.
if (code === props.dataFromParent) {
test = "Test"
}
Hope this helps :)

vue unit test - data not updated after triggering event ( form submit) as expected

I am testing that form data are sent after submit.
ContactForm.spec.js
import Vue from "vue";
import Vuex from "vuex";
import { mount, shallowMount } from "#vue/test-utils";
import VeeValidate from "vee-validate";
import i18n from "#/locales";
import Vuetify from "vuetify";
import ContactForm from "#/components/Home/ContactForm.vue";
Vue.use(VeeValidate, { errorBagName: "errors" });
Vue.use(Vuetify);
Vue.use(Vuex);
describe("ContactForm.vue", () => {
let store;
let wrapper;
let options;
let input;
const v = new VeeValidate.Validator();
beforeEach(() => {
const el = document.createElement("div");
el.setAttribute("data-app", true);
document.body.appendChild(el);
});
it("should sendMessage - valid form", async () => {
// given
store = new Vuex.Store({
state: {
language: "en",
loading: false
}
})
options = {
sync: false,
provide: {
$validator () {
return new VeeValidate.Validator();
}
},
i18n,
store
};
wrapper = mount(ContactForm, options);
// when
const radioInput = wrapper.findAll('input[type="radio"]');
radioInput.at(1).setChecked(); // input element value is changed, v-model is not
radioInput.at(1).trigger("change"); // v-model updated
input = wrapper.find('input[name="givenName"]');
input.element.value = "John"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="familyName"]');
input.element.value = "Doe"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('input[name="email"]');
input.element.value = "john.doe#example.com"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
input = wrapper.find('textarea[name="messageContent"]');
input.element.value = "Hello World!"; // input element value is changed, v-model is not
input.trigger("input"); // v-model updated
const contactForm = wrapper.find("form");
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
console.log("DATA: ", wrapper.vm.$data.contactLang);
expect(wrapper.vm.validForm).toEqual(true);
});
});
Validation is successful ( so validForm is set to true in the component )
BUT the test does not pass
console.log
✕ should sendMessage - valid form (476ms)
● ContactForm.vue › should sendMessage - valid form
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
The component vue is
ContactForm.vue
<template>
<form id="contactForm" #submit="sendMessage()">
<input v-model="contactLang" type='hidden' data-vv-name="contactLang" v-validate="'required'" name='contactLang'>
<v-layout row wrap align-center>
<v-flex xs12 sm3 md3 lg3>
<v-radio-group row :mandatory="false" v-model="gender" name="gender">
<v-radio :label='genderLabel("f")' value="f" name="female"></v-radio>
<v-radio :label='genderLabel("m")' value="m" name="male"></v-radio>
</v-radio-group>
</v-flex>
<v-flex xs12 sm4 md4 lg4>
<v-text-field
v-model="givenName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.givenName')"
data-vv-name="givenName"
:error-messages="errors.collect('givenName')"
v-validate="'required'"
name="givenName">
</v-text-field>
</v-flex>
<v-flex xs12 sm5 md5 lg5>
<v-text-field
v-model="familyName"
browser-autocomplete="off"
:label="$t('lang.views.home.contactForm.familyName')"
data-vv-name="familyName"
:error-messages="errors.collect('familyName')"
v-validate="'required'"
name="familyName">
</v-text-field>
</v-flex>
</v-layout>
<v-text-field
browser-autocomplete="off"
v-model="email"
:label="$t('lang.views.home.contactForm.email')"
data-vv-name="email"
:error-messages="errors.collect('email')"
v-validate="'required|email'"
name="email">
</v-text-field>
<v-textarea v-model="messageContent" :label="$t('lang.views.home.contactForm.message')" :error-messages="errors.collect('messageContent')" :rules="[(v) => v.length <= 200 || 'Max 200 characters']" :counter="200" v-validate="'required'" data-vv-name="messageContent" name="messageContent"></v-textarea>
<v-btn id="btnClear" round #click.native="clear">{{ $t('lang.views.global.clear') }}</v-btn>
<v-btn round large color="primary" type="submit">{{ $t('lang.views.global.send') }}
<v-icon right>email</v-icon><span slot="loader" class="custom-loader"><v-icon light>cached</v-icon></span>
</v-btn>
</form>
</template>
<script>
import swal from "sweetalert2";
import { mapState } from "vuex";
import appValidationDictionarySetup from "#/locales/appValidationDictionary";
export default {
name: "contactForm",
$_veeValidate: { validator: "new" },
data() {
return {
contactLang: "",
gender: "f",
givenName: "",
familyName: "",
email: "",
messageContent: "",
validForm: false
};
},
...
methods: {
...
sendMessage: function() {
console.log("sendMessage()...");
this.$validator
.validateAll()
.then(isValid => {
console.log("VALIDATION RESULT: ", isValid);
this.validForm = isValid;
if (!isValid) {
console.log("VALIDATION ERROR");
// console.log("Errors: ", this.$validator.errors.items.length);
const alertTitle = this.$validator.dictionary.container[
this.language
].custom.error;
const textMsg = this.$validator.dictionary.container[this.language]
.custom.correct_all;
swal({
title: alertTitle,
text: textMsg,
type: "error",
confirmButtonText: "OK"
});
return;
}
console.log("validation success, form submitted validForm: ", this.validForm);
return;
})
.catch(e => {
// catch error from validateAll() promise
console.log("error validation promise: ", e);
this.validForm = false;
return;
});
},
clear: function() {
this.contactLang = "";
this.gender = "f";
this.givenName = "";
this.familyName = "";
this.email = "";
this.messageContent = "";
this.validForm = false;
this.$validator.reset();
}
},
mounted() {
appValidationDictionarySetup(this.$validator);
this.$validator.localize(this.language);
this.contactLang = this.language;
}
};
</script>
</style>
and the console.log debugging is
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
It's weird to see that the DATA ( contactLang ) value is false in the console.log from the spec ... displayed before the validation result
console.log src/components/Home/ContactForm.vue:90
sendMessage()...
console.log tests/unit/ContactForm.spec.js:242
DATA: en
console.log src/components/Home/ContactForm.vue:94
VALIDATION RESULT: true
console.log src/components/Home/ContactForm.vue:112
validation success, form submitted validForm: true
I guess there is async problem ... timout ?
thanks for feedback
SOLVED
It's actually a timeout issue
expect.assertions(1); // Otherwise jest will give a false positive
await contactForm.trigger("submit");
// then
setTimeout(() => {
expect(wrapper.vm.validForm).toEqual(true);
}, 2000);
I propose to use jest faketimers
jest.useFakeTimers()
contactForm.trigger("submit");
await wrapper.vm.$nextTick();
// then
jest.runTimersToTime(2000)
expect(wrapper.vm.validForm).toEqual(true);
I suggest to first make the test fail to avoid false positives
for more information about jest faketimers
https://jestjs.io/docs/en/timer-mocks.html
i did a simple test for my login form of component, in case it helps someone
it("submit form call method login", () => {
const login = jest.fn()
const wrapper = shallowMount(Login, {
methods: {
login
}
})
wrapper.findAll("v-form").at(0).trigger("submit")
expect(login).toBeCalled()
})

How to stop redux-form or React from changing htmlFor and id when creating Jest snapshots?

I've got a wizard form made with redux-forms v6 and it looks something like:
--
index.js - Holds page number in local state, is connected to application level state
PageOne - wrapped with reduxForm decorator (form: 'wizForm')
PageTwo - wrapped with reduxForm decorator (form: 'wizForm')
--
PageOne and PageTwo both contain additional components that render sections of the form (initial fields, vehicle information, driver information...), and each of those sections render their own components for each question in that section.
Since there's a lot of nested components and I want to test that PageOne and PageTwo call the props passed from index.js, I've resorted to using Enzyme's mount() function with a fake store. I want to MatchSnapshot() with Jest to compare whether index.js is rendering PageOne or PageTwo, after certain buttons are clicked to go back and forth from pages.
The problem is when I do create snapshots, other than creating a 16,000 line snapshot, the snapshot will NEVER match the previous one even if I don't change anything. I'm not sure if it's redux-form that's doing it or React, but the htmlFor and the id keep changing between snapshots, test after test after test.
We use css-modules too, but I don't think that's causing the problem, and we did configure Jest to work with css-modules too, modifying "moduleNameWrapper" to mock .css files. Does anyone know how to fix this or where I should look?
tests:
describe('<VehicleAddition />', () => {
let props;
beforeEach(() => {
props = {
...,
};
});
it('Renders initially', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
});
it('Renders <PageTwo> when <PageOne> form is submitted', () => {
const component = shallow(<VehicleAddition {...props} />);
expect(toJson(component)).toMatchSnapshot();
component.find('ReduxForm') // reduxForm HOC wraps the <form> in a <ReduxForm> component
.first()
.simulate('submit');
expect(toJson(component)).toMatchSnapshot();
expect(component.state().page).toEqual(2);
});
it('PageTwoStuffs', () => {
// Render the form, click 'next', assert it's page two
// click 'previous'
jest.enableAutomock();
const store = createStore(
combineReducers({
route: jest.fn(() => Immutable.fromJS({})),
language: jest.fn(() => Immutable.fromJS({})),
global: jest.fn(() => Immutable.fromJS({})),
form: formReducer,
}),
Immutable.fromJS({}),
);
const component = mount(
<Provider store={store}>
<VehicleAddition {...props} />
</Provider>
);
// CAN'T check the state of <VehicleAddition /> because it can only be done on root component, says the error message.
expect(toJson(component)).toMatchSnapshot();
index.js:
export class VehicleAddition extends React.Component { // eslint-disable-line
constructor(props) {
super(props);
this.state = {
page: 1,
};
}
nextPage = () => {
this.setState({ page: this.state.page + 1 });
}
previousPage = () => {
this.setState({ page: this.state.page - 1 });
}
render() {
return (
<div>
{page === 1 &&
<PageOne
{...this.props}
/>
}
{page === 2 &&
<PageTwo
{...this.props}
/>
}
</div>
);
}
}
PageOne.js
class PageOne extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<InitialFields
autoPolicies={autoPolicies}
changeField={this.changeField}
getFormValues={getFormValues}
policies={policies}
primary={primary}
/>
<VehicleBeingAddedFields
changeField={this.changeField}
getFormValues={getFormValues}
fetchVehMakes={fetchVehMakes}
fetchVehModels={fetchVehModels}
policies={policies}
vehMakes={vehMakes}
vehModels={vehModels}
/>
...
<div className="btn-group btn-group-float-right">
<button
type="submit"
onClick={this.handleClick}
disabled={pristine || submitting}
className="btn-primary"
>
Next
</button>
</div>
</form>
);
}
}
PageTwo.js:
class PageTwo extends React.Component { // eslint-disable-line
render() {
const {
...
} = this.props;
return (
<form onSubmit={handleSubmit}>
...
<div className="btn-group btn-group-float-right">
<button type="button" className="btn" onClick={previousPage}>Previous</button>{' '}
<button type="submit" disabled={pristine || submitting} className="btn-primary">Submit</button>
</div>
</form>
);
}
}
Example of the parts of the snapshot that constantly changes:
I solved it by passing a hardcoded id value from the test cases
import React from 'react';
import renderer from 'react-test-renderer';
import { reduxForm } from 'redux-form';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { mount } from 'enzyme'
import TodoItem from './TodoItem';
import injectTapEventPlugin from 'react-tap-event-plugin';
function setup() {
const spy = jest.fn();
const store = createStore(() => ({}));
const Decorated = reduxForm({ form: 'testForm' })(TodoItem);
const props = {
remove: jest.fn(),
TodoItemReduxFormInitialName: "fullName",
snapshotTestId:"4"
}
const mockedComponent = <Provider store={store}>
<Decorated {...props} />
</Provider>;
const enzymeWrapper = mount(mockedComponent)
injectTapEventPlugin();
return {
props,
mockedComponent,
enzymeWrapper
}
}
describe('TodoItem Component', () => {
it('should render the snapshot', () => {
const {mockedComponent} = setup()
const tree = renderer.create(
mockedComponent
).toJSON();
expect(tree).toMatchSnapshot();
});
//not required as snapshot testing covers it
it('should render Number', () => {
const {enzymeWrapper} = setup()
const fieldProps = enzymeWrapper.find('Field').at(0).props();
expect(fieldProps.hintText).toEqual('Item Number');
expect(fieldProps.name).toEqual('fullName.itemNumber');
});
//not required as snapshot testing covers it
it('should render remove button', () => {
const {enzymeWrapper} = setup()
const button = enzymeWrapper.find('RaisedButton').at(0).props();
expect(button.label).toEqual("remove")
});
});

ReactJS Transitions - Why doesn't this work?

I'd like to transition one element as it changes to another element.
I've got 3 examples:
one that works, but uses a list of items that are kept around (jsfiddle)
one that doesnt work, and only keeps one item around, depending on the state (jsfiddle)
another one that doesn't work, that keeps both items around and hides/shows them (jsfiddle using hide/show)
What I want is more like the second one, which is a very slight variation of the first attempt that works.
Option 1:
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1)
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<div><button onClick={this.handleAdd} /></div>
<ReactTransitionGroup transitionName="example">
{items}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<TodoList />, document.body);
Option 2:
JSX that doesn't work, but is closer to what I'd like to do (really, hide one view, and show another)
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
if (this.state.showOne)
{
result = <div ref="a">One</div>
}
else
{
result = <div ref="a">Two</div>
}
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
{result}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
Option 3:
Uses hide/show to keep the 2 views around, but still doesn't work.
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
var c1 = this.state.showOne ? "hide" : "show";
var c2 = this.state.showOne ? "show" : "hide";
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
<div className={c1}>One</div>
<div className={c2}>Two</div>
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
So long story short - How can I make a transition execute on switching from one main "component" to another? I don't get why option 1 works, but option 2 doesn't!
React is just changing the content of the DOM because that's all that changed. Give the elements unique keys to make them animate.
if (this.state.showOne)
{
result = <div key="one">One</div>
}
else
{
result = <div key="two">Two</div>
}
JSFiddle
I used Michelle Treys answer to solve a similar problem using React-Router (1.0.1). Its not clear from the api that the key is needed. I was following React-routers suggestion to render a routes children in a parent as follows:
render() {
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{this.props.children}
</ReactTransitionGroup>
</div>
);
}
However the componentWillEnter only triggered on page load. Following Michelle's solution, I cloned a the children as per the react-router updates and added a key as follows:
render() {
const { location } = this.props;
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{React.cloneElement(this.props.children, {
key: location.pathname,
})}
</ReactTransitionGroup>
</div>
);
}
Thanks for the fix. Cheers