How do I trigger a v-autocomplete "input" event with vue test utils? - jestjs

I'm trying to trigger the "input" event of a Vuetify v-autocomplete component inside of a unit test. From what I can tell, the unit test is quite similar to the one the Vuetify guys are using, but for some reason the mock method is never called.
The factory method simply returns a component instance made via mount and adds a local store, vue, and vueLoading instance.
Would love a bit of insight here, already lost several hours to this and I'm starting to go a bit mad...
Vuetify version: 1.4.3
Component
<template>
<v-autocomplete
:items="options"
:value="selectedId"
:label="label || $t('user.filter')"
:dark="dark"
:class="fieldClass"
:menu-props="{ contentClass }"
:loading="$loading.isLoading('fetch users')"
item-text="email"
item-value="id"
name="search_by_user"
hide-details
single-line
clearable
#input="updateValue($event)"
#click.native="fetchUsers"
/>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
props: {
value: {
type: String,
required: false,
default: null
},
label: {
type: String,
required: false,
default: null
},
dark: {
type: Boolean,
required: false,
default: true
},
fieldClass: {
type: String,
required: false,
default: 'select-user-search'
},
contentClass: {
type: String,
required: false,
default: 'user-search'
},
blankItem: {
type: Object,
required: false,
default: null
}
},
data () {
return {
selectedId: this.value
}
},
computed: {
...mapGetters(['users']),
options () { return this.blankItem ? [this.blankItem].concat(this.users) : this.users }
},
created () {
if (this.value) this.fetchUsers()
},
methods: {
fetchUsers () {
if (this.users.length) return
this.$store.dispatch('FETCH_USERS', {
fields: ['id', 'email'],
filter: { active: 'true' }
})
},
updateValue (value) {
this.selectedId = value
this.$emit('input', value)
}
}
}
</script>
<style>
.select-user-search {
overflow: hidden;
padding-bottom: 1px;
}
.select-user-search .v-select__selections {
overflow: hidden;
}
.select-user-search .v-select__selection {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
Unit Test
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import VueLoading from 'vuex-loading'
import Vuetify from 'vuetify'
import UserSearch from 'manager/components/user/search.vue'
const factory = (values, shallow = true) => {
if (!shallow) {
return mount(UserSearch, { ...values })
}
return shallowMount(UserSearch, { ...values })
}
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueLoading)
localVue.use(Vuetify)
describe('UserSearch', () => {
let actions, getters, store, vueLoading
beforeEach(() => {
actions = {
FETCH_USERS: jest.fn()
}
getters = {
users: () => []
}
vueLoading = new VueLoading({ useVuex: true })
store = new Vuex.Store({
actions,
getters
})
})
it('calls "updateValue" method when input triggered', async () => {
const methods = {
updateValue: jest.fn()
}
const wrapper = factory({ methods, store, localVue, vueLoading }, false)
const input = wrapper.find('input')
input.element.value = 'value'
input.trigger('input')
await wrapper.vm.$nextTick()
expect(methods['updateValue']).toBeCalledWith('value')
})
})

I use the following variant
Component
<template>
<span> <!-- span (or the other HTML element) around <v-autocomplete> is important -->
<v-autocomplete
:items="options"
:value="selectedId"
:label="$t('user.filter')"
:menu-props="{ contentClass }"
:loading="false"
item-text="email"
item-value="id"
name="search_by_user"
hide-details
single-line
clearable
#input="updateValue($event)"
#click.native="fetchUsers"
/>
</span>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import Vue from 'vue';
#Component
export default class AppAutocomplete extends Vue {
private options: any[] = [{email: 'box1#gmail.com', id: 1}];
private selectedId: string|undefined = undefined;
private contentClass = 'user-search';
private fetchUsers() {
}
public updateValue(event: any) {
}
}
</script>
Unit test
import { Wrapper, createLocalVue, mount } from '#vue/test-utils';
import Vue from 'vue';
import Vuetify from 'vuetify';
// #ts-ignore
import AppAutocomplete from '#/components/AppAutocomplete.vue';
describe('AppAutocomplete', () => {
Vue.use(Vuetify);
let wrapper: Wrapper<Vue>;
let vm: any;
beforeEach(() => {
wrapper = mount(AppAutocomplete, {
localVue: createLocalVue(),
vuetify: new Vuetify({
mocks: { $vuetify: { lang: { t: (val: string) => val } } },
}),
mocks: { $t: () => { /** */ } },
});
vm = wrapper.vm;
});
afterEach(() => wrapper.destroy());
it(`calls "updateValue" method when input triggered`, () => {
jest.spyOn(vm, 'updateValue');
const autocompleteElem = wrapper.find('.v-autocomplete');
autocompleteElem.vm.$emit('input', 'box1');
expect(vm.updateValue).toBeCalledTimes(1);
expect(vm.updateValue).toHaveBeenCalledWith('box1');
});
});
To surround v-autocomplete by some other HTML element is important. Without it a test fails.
Vuetify version: 2.1.12

Related

How to add normal modules to Nuxt 3

I am writing a web application in Nuxt 3 and want to integrate a SockJs over Stomp websocket. Now I have written the component...
<template>
<div>
<h1>Test</h1>
</div>
</template>
<script lang="ts" setup>
import {useRouter, ref} from "#imports";
import SockJS from "sockjs-client";
import {Stomp} from "#stomp/stompjs";
let consoleText = ref<string>("");
interface ConsoleMessage {
messageContent: string,
user: string,
}
const props = defineProps<{
originURL:string,
publicURL:string,
privateURL:string
}>();
let stompClient: any = null;
function connect() {
const socket = new SockJS(props.originURL);
stompClient = Stomp.over(socket);
stompClient.connect({}, function () {
stompClient.subscribe(props.publicURL, function (message: { body: string; }) {
showMessage(JSON.parse(message.body));
});
stompClient.subscribe(props.privateURL, function (message: { body: string; }) {
showMessage(JSON.parse(message.body));
});
});
}
function showMessage(message: ConsoleMessage) {
consoleText.value = "\n" + message.messageContent + "\n";
}
connect();
</script>
I have imported #stomp/stompjs and sockjs-client via yarn add. What do I have to change in the nuxt.config.ts to make it so that the application loads the modules.
Edit:
Following the advice given by nur_iyad I attempted to write a plugin
import {defineNuxtPlugin} from "nuxt/app";
import SockJS from "sockjs-client";
import {CompatClient, Stomp} from "#stomp/stompjs";
import {Message} from "~/composables/display";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.provide('sock', () => new NuxtSockJs())
})
declare module '#app' {
interface NuxtApp {
$sock (): NuxtSockJs
}
}
export class NuxtSockJs {
stompInstance: CompatClient | null;
constructor() {
this.stompInstance = null;
}
connect(originURL:string,
subscribeURLs:Array<string>,
displayMessage: (message: Message) => any): void {
const socket = new SockJS(originURL);
let stompClient: CompatClient = Stomp.over(socket);
stompClient.connect({}, function () {
for(const subscribeURL of subscribeURLs) {
stompClient.subscribe(subscribeURL, function (message: { body: string; }) {
displayMessage(JSON.parse(message.body));
});
}
});
this.stompInstance = stompClient;
}
sendMessage(sendURL: string, message: Message):void {
(this.stompInstance)!.send(sendURL, {}, JSON.stringify(message));
}
}
This does not work and just throws the following error:
Uncaught ReferenceError: global is not defined
at node_modules/sockjs-client/lib/utils/browser-crypto.js

In component data is fetched only when i make some changes in it

I am trying to add markers on map, using a view and component
In view where i call an api and then i pass that data to component using v:bind but console.log in that component shows an empty array, when i make some changes in that component the page reloads and data is fetched following is my code in view and then component.
//Script for View
<script>
import Maps from '../components/Maps.vue';
import LoadingOverlay from '../components/loading.vue';
import MessageHelper from '../helpers/messageHelper';
import RepositoryFactory from '../repositories/RepositoryFactory';
const Catalogs = RepositoryFactory.get('catalogs');
export default {
components: {
Maps,
LoadingOverlay,
},
data() {
return {
zones_and_locations: [],
loadingConfig: {
isLoading: true,
cancellable: true,
onCancelMessage: this.onCancelMessage(),
isFullPage: true,
},
};
},
created() {
this.fetchPlacesData();
},
methods: {
async fetchPlacesData() {
const { data } = await Catalogs.getZoneLocations();
if (data.output === null) {
this.nullDataException();
} else {
const places = [];
data.output.forEach((value, index) => {
places.push(value);
});
this.zones_and_locations = places;
this.loadingConfig.isLoading = false;
}
},
onCancelMessage() {
return MessageHelper.getLoadingCancelMessage();
},
nullDataException() {
this.exceptionMessage = 'Data from API is not available.';
console.log(this.exceptionMessage);
},
},
};
</script>
//Script For Map.vue
<script>
import MarkerClusterer from '#google/markerclusterer';
import GoogleMapsHelper from '../helpers/GoogleMapsHelper';
export default {
name: 'GoogleMap',
props: ['zones_and_locations'],
data() {
return {
country: '',
markers: [],
map: '',
};
},
created() {
this.loadGoogleMaps();
},
methods: {
markerClusterer(map, markers) {
return new MarkerClusterer(
map,
markers,
{ imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m' },
);
},
async loadGoogleMaps() {
try {
const google = await GoogleMapsHelper();
const geoCoder = new google.maps.Geocoder();
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
mapTypeControl: false,
fullscreenControl: false,
});
geoCoder.geocode({ address: 'Singapore' }, (results, status) => {
if (status !== 'OK' || !results[0]) {
throw new Error(status);
}
// set Center of Map
map.setCenter(results[0].geometry.location);
map.fitBounds(results[0].geometry.viewport);
});
this.map = map;
let zones = [];
Array.prototype.forEach.call(this.zones_and_locations, child => {
var obj = {
lat: parseFloat(child.lat),
lng: parseFloat(child.lng),
}
var position = {
position: obj,
};
zones.push(position);
});
if (zones.length > 0) {
const markers = zones.map(x => new google.maps.Marker({ ...x, map }));
this.markerClusterer(map, markers);
}
} catch (error) {
console.error(error);
}
},
},
};
</script>
//Template for View
<template>
<div>
<!-- START::Loading Overlay -->
<LoadingOverlay
v-bind:loadingConfig="loadingConfig">
</LoadingOverlay><!-- END::Loading Overlay -->
<Maps v-bind:zones_and_locations="zones_and_locations"
></Maps>
</div>
</template>
//Template for Component
<template>
<div>
<div id="map" class="google-map"></div>
</div>
</template>
Data from the parent component are loaded asynchronously, so the created lifecycle hook inside the component is executed before the actual data comes, when they're still set as an empty array and are not reactive to change.
You can fix this by setting a watch inside the component, like this:
methods: {
...
},
watch: {
zones_and_locations (newVal, oldVal) {
this.loadGoogleMaps();
}
}
orelse you can set a reference to the child component and invoke its method when data comes:
<!-- main view -->
<Maps
v-bind:zones_and_locations="zones_and_locations"
ref="myMap"
></Maps>
async fetchPlacesData() {
const { data } = await Catalogs.getZoneLocations();
if (data.output === null) {
this.nullDataException();
} else {
const places = [];
data.output.forEach((value, index) => {
places.push(value);
});
this.zones_and_locations = places;
// using $refs
this.$refs.myMap.loadGoogleMaps();
this.loadingConfig.isLoading = false;
}
},

commit, but vue prompts me 'mutate vuex store state outside mutation handlers',why?

<template>
<div class="counter-warp">
<p>make</p>
<div class="statusCon">
<p class="card">
last: {{completed.length > 0 ? completed[completed.length - 1].text : ''}}
</p>
<p>completed: {{completed.length}} </p>
<button #click="showAllSentences">showall</button>
</div>
<input v-model="currentText"/>
<p>remain: {{wordsRemain}}</p>
<button #click="addSentence">Next</button>
home
</div>
</template>
<script>
// Use Vuex
import { mapGetters } from 'vuex';
import wxapi from '#/lib/wxapi';
export default {
components: {
},
computed: {
...mapGetters(['wordsRemain', 'completed']),
currentText: {
get() {
return this.$store.state.make.current.text;
},
set(val) {
this.$store.commit('setText', val);
},
},
},
methods: {
addSentence() {
this.$store.commit('addSentence', this.$store.state.make.current);
},
complete() {
this.$store.dispatch('complete').then((workId) => {
wxapi.navigateTo({ url: `/pages/index/main?id=${workId}` });
});
},
},
};
</script>
<style>
.counter-warp {
text-align: center;
margin-top: 100px;
}
.home {
display: inline-block;
margin: 100px auto;
padding: 5px 10px;
color: blue;
border: 1px solid blue;
}
</style>
// store.js
// https://vuex.vuejs.org/zh-cn/intro.html
// make sure to call Vue.use(Vuex) if using a module system
import Sentence from '#/lib/Sentence';
import Constants from '#/lib/Constants';
import { Work, User } from '#/lib';
//
const ROW_LENGTH = {
portrait: Constants.WINDOW_WIDTH,
landscape: Constants.WINDOW_HEIGHT,
};
const store = {
state: {
orientation: 'portrait',
current: new Sentence(),
sentences: [],
},
getters: {
completed: state => state.sentences,
sentencesLength: state => state.sentences.length,
wordsRemain: (state) => {
const fontSize = state.current.fontSize;
const marginTextLength = Constants.MARGIN * fontSize;
const remainPx = ROW_LENGTH[state.orientation] - marginTextLength;
const textLimit = Math.floor(remainPx / fontSize);
return textLimit - state.current.text.length;
},
//
lastSentence: (state, getters) => {
const obj = state;
return obj.sentences[getters.sentencesLength - 1];
},
},
mutations: {
addSentence: (state, sentence) => {
state.sentences.push(sentence);
state.current = new Sentence();
console.log(state);
},
setText: (state, text) => {
state.current.text = text;
},
},
actions: {
complete: ({ state, commit }) => {
// commit('setText',)
// commit('addSentence', state.current);
const config = Object.assign({}, state);
commit('setConfig', config);
const work = new Work();
work.set('author', User.current);
work.set('config', config);
return work.save().then(obj => Promise.resolve(obj.id), (err) => {
console.log(err);
});
},
},
};
export default store;
When I click the 'next' button,the 'addSentence' mutation handler should be called,but vue prompt me that'[vuex] Do not mutate vuex store state outside mutation handlers.
Error: [vuex] Do not mutate vuex store state outside mutation handlers.'
Sentence is a simple class like { text:'' ,sth: {}};.
I think the problem is within the component method:
methods: {
addSentence() {
this.$store.commit('addSentence', this.$store.state.make.current);
},
complete() {
this.$store.dispatch('complete').then((workId) => {
wxapi.navigateTo({ url: `/pages/index/main?id=${workId}` });
});
} ,
},
When you call this.$store.state.make.current, this is modifying the store directly. Rather than adding a payload to addSentence(), all you need to do is get the store to create the current Sentence directly within the mutation.
By the way, you should not call commit directly from the component. You need to call a dispatch action on the store and then commit the mutation via that.
The pattern would be:
COMPONENT:
methods: {
addSentence () {
this.$store.dispatch('attemptAddSentence');
},
STORE:
actions: {
attemptAddSentence (({state, commit}) => {
return new Promise((resolve, reject) => {
commit(‘addSentence’)
resolve()
})
}),
mutations: {
addSentence (state) => {
// Build sentence here...
}),
Hope that helps.

Action creator is being dispatched but the new state is not showing in store

I have written a server and wired up React with Redux, also made use of container-componenent-seperation. The first action fetchSnus() is successfully dispatched, the selectors also seem to work, but for some reason all the actions that I call after the first rendering, such as fetchSnu() (that only fetches ONE single snu-object) e.g. is not accessible for me in the store, meaning: after mapping state and dispatch to props not accessible for me under this.props. I really don't understand why!
You can see the whole project here: https://github.com/julibi/shonagon
This is my container ReadSingleSnuContainer.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchSnus, fetchSnu, setToRead, createSnu, getSnusMatchingKeyword } from '../../actions/index';
import { unreadSnus, randomFirstSnu } from '../../selectors/index';
import ReadSingleSnu from './ReadSingleSnu';
class ReadSingleSnuContainer extends Component {
componentWillMount() {
this.props.fetchSnus();
}
render() {
return (
<ReadSingleSnu { ...this.props } />
);
}
}
const mapStateToProps = (state) => {
return {
snus: state.snus,
randomFirstSnu: randomFirstSnu(state),
candidate: state.candidate
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchSnus: () => dispatch(fetchSnus()),
getSnusMatchingKeyword,
fetchSnu,
setToRead,
createSnu
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReadSingleSnuContainer);
This is my component ReadSingleSnu.js:
import React, { Component } from 'react';
import style from './ReadSingleSnu.css'
import Typist from 'react-typist';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class ReadSingleSnu extends Component {
constructor(props) {
super(props);
this.state = { showTitles: false };
}
renderRandomFirstSnu() {
const { randomFirstSnu } = this.props;
const { showTitles } = this.state;
// preselect a keyword
// dispatch the action that searches for other keywords (action I)
if(randomFirstSnu) {
return (
<div>
<h3><Typist cursor={ { show: false } }>{ randomFirstSnu.title }</Typist></h3>
<ReactCSSTransitionGroup
transitionName="snu"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<p>{ randomFirstSnu.text }</p>
</ReactCSSTransitionGroup>
{ !showTitles ? (
<div>
<button>Some other</button>
<button onClick={ () => this.handleDoneReading(randomFirstSnu) }>Done reading, next</button>
</div>
) : (
<ReactCSSTransitionGroup
transitionName="keywords"
transitionAppear={ true }
transitionAppearTimeout={ 1000 }
transitionEnter={ false }
transitionLeave={ false }
>
<ul>{ randomFirstSnu.keywords.map((keyword, idx) =>
<li key={ idx }>
<button onClick={ () => this.fetchNextSnu(randomFirstSnu) }>
{ keyword }
</button>
</li>) }
</ul>
</ReactCSSTransitionGroup>
)
}
</div>
);
}
return <div>Loading ...</div>
}
handleDoneReading(snu) {
const { setToRead, getSnusMatchingKeyword } = this.props;
const id = snu._id;
if (snu.keywords.length > 0 && setToRead) {
// setToRead(id, snu);
this.setState({ showTitles: true });
const randomIndex = Math.floor(Math.random() * snu.keywords.length);
const randomKeyword = snu.keywords[randomIndex];
console.log('This is the randomKeyword :', randomKeyword);
getSnusMatchingKeyword(randomKeyword);
} else {
console.log('Here will soon be the select random next snu action :)');
}
}
render() {
console.log('ReadSingleSnu, this.props: ', this.props);
return (
<div className={style.App}>
<div>{ this.renderRandomFirstSnu() }</div>
</div>
);
}
}
This is my actions file:
import axios from 'axios';
export const FETCH_SNUS = 'FETCH_SNUS';
export const FETCH_SNU = 'FETCH_SNU';
export const SET_TO_READ = 'SET_TO_READ';
export const CREATE_SNU = 'CREATE_SNU';
export function getSnusMatchingKeyword(keyword) {
const request = axios.get(`/snus/keyword/${keyword}`);
return {
type: GET_SNUS_MATCHING_KEYWORD,
payload: request
};
}
export function fetchSnus() {
const request = axios.get('/snus');
return {
type: FETCH_SNUS,
payload: request
};
}
export function fetchSnu(id) {
const request = axios.get(`/snus/${id}`);
return {
type: FETCH_SNU,
payload: request
};
}
export function setToRead(id, snu) {
const request = axios.patch(`/snus/${id}`, { title: snu.title, text: snu.text, keywords: snu.keywords, read: true });
return {
type: SET_TO_READ,
payload: request
}
}
export function createSnu(object) {
const request = axios.post('/snus', object);
return {
type: CREATE_SNU,
payload: request
};
}
A Reducer:
import { FETCH_SNUS, FETCH_SNU, SET_TO_READ, CREATE_SNU } from '../actions/index';
export default function(state = [], action) {
switch(action.type) {
case FETCH_SNUS:
return [ ...state, ...action.payload.data ];
case FETCH_SNU:
return [ ...state, ...action.payload.data ];
case SET_TO_READ:
return [ ...state, ...action.payload.data ];
case CREATE_SNU:
return [...state, ...action.payload.data ];
default:
return state;
}
}
I tested all endpoints via Postman and they work. So that should not be the problem… Please help! I cannot find a solution to this problem.

redux-form handleSubmit not sending form data

handleSubmit() isn't sending form data. It appears the configuration object for the fields are undefined for some reason, even though i believe i wired up redux-form correctly.
LoginComponent.js
import { reduxForm } from 'redux-form';
import '../others/styles.css';
const FIELDS = {
username: {
type: 'input',
label: 'Enter username'
},
password: {
type: 'input',
label: 'Enter password'
}
};
const Login = (props) => {
console.log('--------- props: ', props);
const { fields: { username, password }, handleSubmit, setUsernameAndPassword } = props;
console.log('-------- username: ', username); // PROBLEM: Returns undefined when it should return the config object for this field
return (
<div>
<Form onSubmit={ handleSubmit(setUsernameAndPassword.bind(this)) } id='login'>
<Form.Field>
<label>Username</label>
<input {...username}
name='username' />
</Form.Field>
....
Login.propTypes = {
handleSubmit: React.PropTypes.func,
fields: React.PropTypes.array,
setUsernameAndPassword: React.PropTypes.func
};
export default reduxForm({
form: 'LoginForm',
fields: Object.keys(FIELDS)
})(Login);
LoginContainer.js
import { connect } from 'react-redux';
import { graphql } from 'react-apollo';
import Login from '../components/LoginComponent';
import LoginMutation from '../graphql/LoginMutation.gql';
import { types as typesAuth } from '../reducers/auth';
const gqlLogin = graphql( LoginMutation, {
props: ({mutate}) => ({
login: async function loginWithUsernameOrEmail(variables) {
try {
const result = await mutate({variables});
} catch (err) {
console.log('GQL Error: ', err);
}
}
})
})(Login);
export default connect(
state => ({
isLoggedIn: state.auth.isLoggedIn
}),
dispatch => ({
setUsernameAndPassword: (data) => { // PROBLEM: Why is data empty??
console.log('--------- data: ', data);
dispatch({
type: typesAuth.SET_USERNAME_PASSWORD,
payload: {
username: data.username,
password: data.password
}
});
}
}),
)(gqlLogin);
reducers/index.js
import { reducer as auth } from './auth';
import { reducer as formReducer } from 'redux-form';
export default {
auth,
form: formReducer
};
reducers.js
// Modules
import appReducers from '../modules/root/reducers/';
import authReducers from '../modules/auth/reducers/';
module.exports = {
...appReducers,
...authReducers
};
redux.js
// Imports
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
// Reducers
import Client from './client';
import reducers from './reducers';
const devtools = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
module.exports = createStore(
combineReducers({
...reducers,
apollo: Client.reducer()
}),
compose(
applyMiddleware(Client.middleware()), devtools()
),
);
auth.js
// Constants
export const types = Object.freeze({
SET_USERNAME_PASSWORD: 'AUTH/SET_USERNAME_PASSWORD'
});
// Default State
const DEF_STATE = {
isLoggedIn: false,
username: null,
password: null
};
export const reducer = (state = DEF_STATE, action) => {
let newState;
switch (action.type) {
...
case types.SET_USERNAME_PASSWORD:
newState = {
...state,
username: action.payload.username,
password: action.payload.password
};
break;
default:
newState = state;
}
return newState;
};
You need to use Field components from redux-form to tie the individual inputs to the redux store (see docs).
The "Simple Form" Example in the redux-form docs is a good demonstration of how to do this, simply import the Field component from redux-form and use it to describe your inputs. In your case it would be something like this:
import { reduxForm, Field } from 'redux-form';
const Login = (props) => {
console.log('--------- props: ', props);
const { handleSubmit, isLoggingIn, setUsernameAndPassword } = props;
return (
<div>
<Form onSubmit={ handleSubmit(setUsernameAndPassword.bind(this)) } id='login'>
<Form.Field>
<label>{i18n.t('auth.username')}</label>
<Field
component="input"
name="username"
placeholder={i18n.t('utils.and',{item1: i18n.t('auth.username'), item2: i18n.t('auth.email')})}
/>
</Form.Field>
....
Hope this helps!

Resources