I am trying to combine navigation, more exactly stack navigation with my react native redux application, and I encountered this error during debugging. From what I've already searched, it's because the navigation isn't really compatible with the redux mode of work. So I tried a solution to my problem, but still I have the same error. Here is my code:
Login.js
import React, { Component } from 'react';
import { View, ActivityIndicator, TouchableHighlight } from 'react-native';
import { getLogger, issueToText } from '../core/utils';
import styles from '../core/styles';
import { Card, Button, FormLabel, FormInput } from "react-native-elements";
import { connect } from 'react-redux'
import { loginAction } from '../actions/LoginActions'
class LoginComponent extends Component {
constructor(props) {
super(props);
this.login = this.login.bind(this)
}
render() {
const { error, isLoading } = this.props;
const inputFormProp = {
username: '',
password: ''
};
return (
<View style={{ paddingVertical: 20 }}>
<Card>
<FormLabel>Email</FormLabel>
<FormInput value={inputFormProp.username} onChangeText={(text) => inputFormProp.username = text} />
<FormLabel>Password</FormLabel>
<FormInput value={inputFormProp.password} onChangeText={(text) => inputFormProp.password = text} />
<Button
buttonStyle={{ marginTop: 20 }}
backgroundColor="#03A9F4"
title="SIGN IN"
onPress={this.login(inputFormProp)}
/>
</Card>
<ActivityIndicator animating={this.props.isLoading} style={styles.activityIndicator} size="large" />
</View>
);
}
login(inputFormProp) {
console.log(this.props)
const { store } = this.props.screenProps.store;
console.log(loginAction)
const { dispatch } = this.props
console.log(dispatch)
dispatch(loginAction(inputFormProp))
.then(() => {
if (this.props.error === null && this.props.isLoading === false) {
if (store.getState().auth.token) {
this.props.navigation.navigate('ProductList', { token: store.getState().auth.token });
}
}
})
.catch(error => {
});
}
}
function mapStateToProps(state) {
const { error, isLoading } = state.auth
return {
error,
isLoading,
}
}
export default connect(mapStateToProps)(LoginComponent)
App.js
import React, { Component } from 'react';
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import { authReducer } from "./src/reducers/LoginReducer";
import { productReducer } from "./src/product/service";
import { ProductList } from "./src/product/ProductList";
import { LoginComponent } from "./src/components/Login";
import { Provider, connect } from "react-redux";
import { StackNavigator, addNavigationHelpers } from "react-navigation";
import Routes from "./src/core/routes";
const AppNavigator = StackNavigator(Routes, {
navigationOptions: {
title: ({ state }) => {
if (state.params) {
return `${state.params.title}`;
}
}
}
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const initialState = {
auth: { isLoading: false, error: null },
};
const rootReducer = combineReducers({ product: productReducer, auth: authReducer, nav: navReducer });
let store = createStore(rootReducer, initialState,
compose(applyMiddleware(thunk, createLogger())));
export default function App() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
Routes.js
import { ProductList } from "../product/ProductList";
import { LoginComponent } from "../components/Login";
const Routes = {
Login: { screen: LoginComponent },
ProductList: { screen: ProductList }
};
export default Routes;
Here is my error: Route Login should declare a screen.
What did I do wrong with my code? Thank you.
I fixed the error. It was because I added between LoginComponent {} in the routes.js file at:
import { LoginComponent } from "../components/Login";
Related
I'm new to programming and following this video, my code until now has had no problems, but when I try
console.log(currentUser);
It returns undefined while it shouldn't, the user is logged in and all of their information is correct in Firebase. Also, my code doesn't show any errors.
This is the code:
import React, { Component } from 'react';
import {Text, View} from 'react-native';
import firebase from 'firebase'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchUser } from '../../redux/actions/index';
export class Main extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
const { currentUser } = this.props;
console.log(currentUser)
if (currentUser==undefined){
return(
<View></View>)
}
return (
<View style={{ flex: 1, justifyContent: 'center'}}>
<Text>User is logged in</Text>
</View>
)
}
}
const mapStateToProps = (store) => ({
currentUser: store.userState.currentUser
})
const mapDispatchProps = (dispatch) => bindActionCreators({fetchUser}, dispatch)
export default connect(null, mapDispatchProps)(Main);
And this is the parent component code:
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
require('dotenv').config();
import firebase from 'firebase';
const firebaseConfig = {
//personal info
};
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './redux/reducers';
import thunk from 'redux-thunk';
const store = createStore(rootReducer, applyMiddleware(thunk))
if(firebase.apps.length === 0){
firebase.initializeApp(firebaseConfig)
}
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import {createMaterialTopTabNavigator} from 'react-navigation-tabs';
import LandingScreen from './Components/auth/Landing';
import RegisterScreen from './Components/auth/Register';
import MainScreen from './Components/auth/Main';
const Stack = createStackNavigator();
export class App extends Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged((user) => {
if (!user){
this.setState({
loggedIn: false,
loaded: true,
})
}else{
this.setState({
loggedIn: true,
loaded: true,
})
}
})
}
render() {
const { loggedIn, loaded } = this.state
if (!loaded){
return(
<View style={{ flex: 1, justifyContent: 'center'}}>
<Text>Loading</Text>
</View>
)
}
if (!loggedIn) {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Landing">
<Stack.Screen name="Landing" component={LandingScreen} options={{headerShown: false}}/>
<Stack.Screen name="Register" component={RegisterScreen}/>
</Stack.Navigator>
</NavigationContainer>
);
}
return(
<Provider store={store}>
<MainScreen/>
</Provider>
)
}
};
export default App
I'm new to redux (and stack-overflow) and am having some issues with my search functionality. Originally it was working fine with data that I manually set, but then I imported Data from an API and I cannot get the search to work now. Any help or advice would be much appreciated!
<-- actions -->
//fetchData.js
import {
FETCH_DATA_REQUEST,
FETCH_DATA_SUCCESS,
FETCH_DATA_FAILURE,
SEARCH_POSTS
} from './types';
export const fetchData = () => {
return (dispatch) => {
dispatch(fetchDataRequest())
axios
.get("https://hn.algolia.com/api/v1/search?query=redux")
.then(response => {
const posts = response.data
dispatch(fetchDataSuccess(posts))
})
.catch(error => {
dispatch(fetchDataFailure(error.message))
})
}
}
export const fetchDataRequest = () => {
return {
type: FETCH_DATA_REQUEST
}
}
const fetchDataSuccess = posts => {
return {
type: FETCH_DATA_SUCCESS,
payload: posts
}
}
const fetchDataFailure = error => {
return {
type: FETCH_DATA_FAILURE,
payload: error
}
}
export function searchData(value) {
return {
type: SEARCH_POSTS,
payload: value
}
}
<--- components --->
//dataContainer.js
import { connect } from 'react-redux';
import { fetchData } from '../actions/fetchData';
import { Card } from 'react-bootstrap';
import { Button } from 'react-bootstrap';
function DataContainer({ results, fetchData }) {
useEffect(() => {
fetchData()
}, [])
return results.loading ? (
<h2>Loading...</h2>
) : results.error ? (
<h2>{results.error}</h2>
) : (
<div>
<h1>Posts</h1>
{results && results.posts && results.posts.map(result =>
<div className ="cardDiv" key={result.objectID}>
<Card>
<Card.Header>By: {result.author}</Card.Header>
<Card.Body>
<Card.Title>{result.title}</Card.Title>
<Card.Text>
{result.body}
</Card.Text>
<Button variant="primary" href={result.url}>See Article</Button>
</Card.Body>
</Card>
{'\n'}
</div>)}
</div>
)
}
const mapStateToProps = state => {
return {
results: state.data
}
}
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(fetchData())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);
//searchBar.js
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { searchData, fetchData } from '../actions/fetchData';
function SearchBar({ posts, fetchData}) {
useEffect(() => {
fetchData()
}, [])
const { search, value } = posts;
return (
<input
className="form-control mx-auto"
placeholder="Search"
onChange={(e) => searchData(e.target.value)}
value={value}
style={{ maxWidth: '200px', textAlign: 'center' }} />
);
}
function mapStateToProps({ posts, state }) {
return {
posts: state.posts,
value: posts.value
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ searchData, fetchData }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
//homePage.js
import SearchBar from './searchBar';
import DataContainer from './dataContainer';
import { connect } from 'react-redux';
import * as actions from '../actions/fetchData';
class HomePage extends Component {
handleSearchBarSubmit(query) {
this.props.fetchPostsWithQuery(query, () => {
this.props.history.push('/results');
});
}
render() {
return (
<div className="home">
<SearchBar page="home"/>
<DataContainer/>
</div>
);
}
}
export default connect(null, actions)(HomePage);
<--- Reducers --->
//dataReducer
FETCH_DATA_REQUEST,
FETCH_DATA_SUCCESS,
FETCH_DATA_FAILURE,
SEARCH_POSTS
} from "../actions/types";
const _ = require('lodash')
const posts = []
const initState = {
loading: false,
posts,
error: '',
filtered: []
}
const reducer = (state = initState, action) => {
switch (action.type) {
case FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_DATA_SUCCESS:
return {
loading: false,
posts: action.payload.hits,
filtered: action.payload.hits,
error: ''
}
case FETCH_DATA_FAILURE:
return {
loading: false,
posts: [],
error: action.payload
}
case SEARCH_POSTS:
const { payload } = action
const filtered = _.filter(state.data.posts, (o) => _.toLower(o.post).includes(_.toLower(payload)))
return {
...state,
filtered
}
default: return state
}
}
export default reducer;
//searchReducer.js
SEARCH_POSTS
} from '../actions/types';
const posts = []
const INIT_STATE = {
posts
}
export default function (state = INIT_STATE, action) {
switch (action.type) {
case SEARCH_POSTS: {
let { value } = action.payload.hits;
const posts = state.posts.filter((post) => post.title.toLowerCase().includes(state.value.toLowerCase()));
return { ...state, value, posts };
}
default:
return state;
}
}
//rootReducer.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form'
import resultsPosts from './searchReducer';
const rootReducer = combineReducers({
data: dataReducer,
resultsPosts,
form
})
export default rootReducer;
<--- App.js and Index.js --->
//app.js
import './App.css';
import HomePage from './components/homePage';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import ResultPage from './components/resultPage'
function App() {
return (
<BrowserRouter>
<div className='App'>
<h1 style={{textAlign: 'center'}}>Search for Hacker News!</h1>
<Switch>
<Route path="/" exact={true} component={HomePage} />
<Route path='/results/:id' component={ResultPage}/>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
//index.js
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
How can I access the field values in a parent component of a redux-form component?
I'm not sure if it's caused by typescript, but before I started using typescript, I was able to access the form values through mapStateToProps just like how I have it currently. I've been trying to figure out what was different to my previous implementation but the only difference would be the versions of the npm dependencies and the addition of typescript.
LoginPage.tsx
import LoginForm from 'components/Forms/LoginForm'
import Layout from 'components/Layout'
import { StatusCodes } from 'lib/enums/statusCodes'
import { storeAuthToken } from 'lib/helpers/auth'
import { NextPage } from 'next'
import Router from 'next/router'
import React from 'react'
import { connect, DispatchProp } from 'react-redux'
import { FormInstance } from 'redux-form'
interface IProps {
login: FormInstance<IFormData, IFormProps>
}
interface IState {
errorMessage?: string,
processing: boolean
}
interface IRootState {
form: IProps
}
export interface IFormData {
username?: string,
password?: string
}
export interface IFormProps {
contactId?: string,
errorMessage?: string,
fieldValues: Partial<IFormData>,
processing: boolean
}
class LoginPage extends React.Component<NextPage & DispatchProp & IProps, IState> {
state = {
errorMessage: undefined,
processing: false
}
setErrorMessage = (message: string) => {
this.setState({
errorMessage: message,
processing: false
})
}
handleSubmit = async (values: IFormData) => {
if (values && values.username && values.password) {
this.setState({
errorMessage: undefined,
processing: true
})
try {
const { dispatch } = this.props
await storeAuthToken(dispatch, values.username, values.password)
Router.push('/')
} catch (error) {
if (error === StatusCodes.BAD_REQUEST) {
this.setErrorMessage("Sorry, you have entered incorrect details. Please try again.")
} else {
this.setErrorMessage("Sorry, there was an issue trying to log you in")
}
}
}
}
render() {
const { login } = this.props
const { processing } = this.state
return (
<Layout title="Login">
<div className="form-wrapper full">
<LoginForm processing={processing} onSubmit={this.handleSubmit} fieldValues={login.values} />
</div>
</Layout>
)
}
}
const mapStateToProps = ({ form: { login } }: IRootState) => ({ login })
export default connect(mapStateToProps)(LoginPage)
LoginForm.tsx
import Link from 'next/link'
import React from 'react'
import { Field, InjectedFormProps, reduxForm } from 'redux-form'
import FormButton from 'components/Forms/FormButton'
import Input from 'components/Fields/Input'
import { validateRequired } from 'lib/helpers/validators'
import { IFormProps, IFormData } from 'pages/login'
class LoginForm extends React.Component<IFormProps & InjectedFormProps<IFormData, IFormProps>> {
render() {
const { contactId, errorMessage, fieldValues, handleSubmit, processing } = this.props
return (
<form id="login" onSubmit={handleSubmit} >
<h1>Sign in</h1>
<fieldset>
<div className="fields">
{
!contactId
? <Field name="username" type="text" component={Input} label="Username" validate={validateRequired} />
: <Field name="username" type="email" component={Input} label="Email" validate={validateRequired} />
}
</div>
<div className="fields">
<Field name="password" type="password" component={Input} label="Password" validate={validateRequired} />
</div>
</fieldset>
{ errorMessage && <p className="error-message">{errorMessage}</p> }
<div className="form-bottom">
<Link href="/"/*{`/forgot-password${fields.email ? `?email=${encodeURIComponent(fields.email)}` : ''}`}*/>
<a className="inline">Forgotten your password?</a>
</Link>
<FormButton loading={processing}>
Login
</FormButton>
</div>
</form>
)
}
}
export default reduxForm<{}, IFormProps>({ form: 'login' })(LoginForm)
Here is my redux store file incase if that is coded incorrectly
import { createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper'
import { AnyAction, applyMiddleware, combineReducers, createStore, Reducer } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk'
import authReducer, { AuthState } from './auth/reducer'
import contactReducer, { ContactState } from './contact/reducer'
import initialState from './initialState'
export interface State {
auth: AuthState
contact: ContactState
}
const bindMiddleware = (middleware: [ThunkMiddleware]) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
auth: authReducer,
contact: contactReducer,
form: formReducer
})
const reducer: Reducer = (state: State, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: Reducer = {
...state,
...action.payload
}
return nextState
} else {
return combinedReducer
}
}
const makeStore: MakeStore<State> = () => createStore(reducer, initialState, bindMiddleware([thunkMiddleware]))
export const wrapper = createWrapper<State>(makeStore/*, { debug: true }*/)
It seems like I missed out a key in the IApplicationState interface and as mentioned by #cbr, the parameters state and action needed to be passed to combinedReducer even though it doesn't directly take any.
Additionally it didn't like when the nextState constant had the type Reducer, so I have changed that to CombinedState<State> as well
The changed code looks like this
import { createWrapper, HYDRATE, MakeStore } from 'next-redux-wrapper'
import { AnyAction, applyMiddleware, combineReducers, createStore, Reducer } from 'redux'
import { reducer as formReducer } from 'redux-form'
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk'
import authReducer, { AuthState } from './auth/reducer'
import contactReducer, { ContactState } from './contact/reducer'
import initialState from './initialState'
export interface State {
auth: AuthState
contact: ContactState,
form: FormStateMap
}
const bindMiddleware = (middleware: [ThunkMiddleware]) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
auth: authReducer,
contact: contactReducer,
form: formReducer
})
const reducer: Reducer = (state: State, action: AnyAction) => {
if (action.type === HYDRATE) {
const nextState: CombinedState<State> = {
...state,
...action.payload
}
return nextState
} else {
return combinedReducer(state, action)
}
}
const makeStore: MakeStore<State> = () => createStore(reducer, initialState, bindMiddleware([thunkMiddleware]))
export const wrapper = createWrapper<State>(makeStore)
I have component App which render its children and Header component. I use Preloader from react-loader repo which takes bool loaded and render preloader or page in depended from bool. When App componentWillMount, data fetch via actionCreators, action use redux-api-middleware, then when execute render in App, Header fetch data via actionCreator boundGetPhotos which execute recursively look PHOTOS_GET_SUCCESS in console screenshot here i log action.type in my fetchingMiddleware . All actions pass from my middleware fetchingMiddleware look belowe. Which can be reasons of recursive behavior why it execute again and again and how i can solve it
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { bindActionCreators } from 'redux';
import { getFriends, getMessages } from '../data/Data.Actions';
import { getUsers } from '../users/Users.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const { boundGetFriends, boundGetMessages, boundGetUsers } = this.props;
boundGetFriends();
boundGetMessages();
boundGetUsers();
}
render() {
const { children, fetching } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header/>
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
boundGetUsers: PropTypes.func,
boundGetMessages: PropTypes.func,
boundGetFriends: PropTypes.func,
fetching: PropTypes.array
};
export default connect((store) => {
return {
fetching: store.fetching
};
}, (dispatch) => {
return {
boundGetUsers: bindActionCreators(getUsers, dispatch),
boundGetFriends: bindActionCreators(getMessages, dispatch),
boundGetMessages: bindActionCreators(getFriends, dispatch)
};
})(App);
Header
import React, { Component, PropTypes } from 'react';
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { getPhotos } from '../user/User.Actions';
class Header extends Component {
componentWillMount() {
const { boundGetPhotos } = this.props;
boundGetPhotos();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
MyProject
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
boundGetPhotos: PropTypes.func.isRequired
};
export default connect((store) => null, (dispatch) => {
return {
boundGetPhotos: bindActionCreators(getPhotos, dispatch)
};
})(Header);
FetchingMiddleware
import { startFetching, endFetching } from './FetchingMiddleware.Actions';
export default store => next => action => {
console.log(action.type);
if (typeof action !== 'function' && action.type.search(/REQUEST/) !== -1) {
store.dispatch(startFetching());
}
if (typeof action !== 'function' && action.type.search(/SUCCESS|FAILURE/) !== -1) {
store.dispatch(endFetching());
}
next(action);
};
FetchingMiddlewareReducers
import Immutable from 'immutable';
import { END_FETCHING, START_FETCHING, RESET_FETCHING } from './FetchingMiddleware.Actions';
import createReducer from '../utils/utils';
function addFetching(state, action) {
return state.push(true);
}
function removeFetching(state, action) {
return state.pop();
}
function resetFetching(state, action) {
return Immutable.List();
}
export default createReducer({
[END_FETCHING]: removeFetching,
[START_FETCHING]: addFetching,
[RESET_FETCHING]: resetFetching
}, Immutable.List());
FetchingMiddlewareActions
export const END_FETCHING = 'END_FETCHING';
export const START_FETCHING = 'START_FETCHING';
export const RESET_FETCHING = 'RESET_FETCHING';
export function endFetching() {
return {
type: END_FETCHING
};
}
export function startFetching() {
return {
type: START_FETCHING
};
}
export function resetFetching() {
return {
type: RESET_FETCHING
};
}
getPhotos
import { CALL_API } from 'redux-api-middleware';
export const PHOTOS_GET_SUCCESS = 'PHOTOS_GET_SUCCESS';
export function getPhotos() {
return {
[CALL_API]: {
endpoint: '/photos',
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
types: ['REQUESTPHOTOS', PHOTOS_GET_SUCCESS, 'FAILURE']
}
};
}
Your <Header /> component should be a pure component that knows nothing about your state container (Redux) or dispatch.
Using the approach you have here would litter your component tree with 'connect' and add awareness of Redux to all of your components. This is bad practice in terms of scalability - what if you wanted to replace Redux with another state container?.
I would recommend that all state and actions should be bound to props and passed down the tree into your components such as the <Header /> component.
This should also resolve the issues you are having.
App
import React, { Component, PropTypes } from 'react';
import Counterpart from 'counterpart';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import { getMasterDataSchema, getMasterDataData } from '../masterdata/MasterData.Actions';
import { getQuestionnaireSchema } from '../questionnaireschema/QuestionnaireSchema.Actions';
import Header from './Header';
class App extends Component {
componentWillMount() {
const {
GetMasterDataData,
GetMasterDataSchema,
GetQuestionnaireSchema
} = this.props;
GetMasterDataData();
GetMasterDataSchema();
GetQuestionnaireSchema();
}
render() {
const { children, fetching, GetPrincipal } = this.props;
return (
<Loader loaded={!fetching.size}>
<div>
<Header GetPrincipal={GetPrincipal} />
{children}
</div>
</Loader>
);
}
}
App.propTypes = {
GetPrincipal: PropTypes.func,
GetQuestionnaireSchema: PropTypes.func,
GetMasterDataSchema: PropTypes.func,
GetMasterDataData: PropTypes.func,
fetching: PropTypes.array
};
export default connect(({ fetching }) => ({
fetching
}), {
GetPrincipal,
GetQuestionnaireSchema,
GetMasterDataData,
GetMasterDataSchema,
})(App);
Header
import React, { Component, PropTypes } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
export default class Header extends Component {
componentWillMount() {
const { GetPrincipal } = this.props;
GetPrincipal();
}
render() {
return (
<Navbar fluid collapseOnSelect>
<Navbar.Brand>
EMS
</Navbar.Brand>
</Navbar>
);
}
}
Header.propTypes = {
GetPrincipal: PropTypes.func.isRequired
};
I have been trying to learn to This is a simple login, logout app using Redux. On pressing the Login Button from Display.js, the login_action function should be called. But an error is showing with title Cannot read property login_action of undefined. I tried logging the props in Display.js and I am able to see the functions in the logs but somehow the functions aren't being called. What is it that I'm missing or unable to find out?
Basic App :
/* App.js */
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';
import SceneContainer from './containers/SceneContainer';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<SceneContainer />
</Provider>
);
}
}
Container:
/* containers/SceneContainer.js */
'use strict';
import React, {Component, PropTypes} from 'react';
import Display from '../components/display';
import * as loginActions from '../actions/';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class SceneContainer extends Component {
constructor(props) {
super(props);
}
render() {
const {actions} = this.props;
console.log(actions);
return (
<Display {...actions}/>
);
}
}
SceneContainer.propTypes = {
user: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {user: state.auth.user};
}
function mapDispatchToProps(dispatch) {
console.log(loginActions);
return {
actions: bindActionCreators(loginActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SceneContainer);
Component :
/* components/display.js */
import React, {Component, PropTypes} from 'react';
import {
View,
Text,
StyleSheet,
TouchableHighlight,
} from 'react-native';
class Display extends Component {
constructor(props) {
super(props);
console.log(props.login_action);
}
onLoginPress() {
this.props.login_action({
username: 'ranat',
password: 'password'
});
}
onLogoutPress() {
this.props.logout_action();
}
render() {
return (
<View>
<TouchableHighlight onPress={this.onLoginPress}>
<Text>Login</Text>
</TouchableHighlight>
<TouchableHighlight onPress={this.onLogoutPress}>
<Text>Logout</Text>
</TouchableHighlight>
</View>
);
}
}
Display.propTypes = {
logout_action: PropTypes.func.isRequired,
login_action: PropTypes.func.isRequired
};
export default Display;
Actions file :
/* actions/index.js */
import {LOGIN_ACTION, LOGOUT_ACTION, LOGIN_SUCCESS, LOGIN_FAILURE} from './actionTypes';
export var login_action = (userCredentials) => {
if(userCredentials.username === 'ranat' && userCredentials.password === 'password') {
return {
type: LOGIN_ACTION,
value: LOGIN_SUCCESS,
};
}
else {
return {
type: LOGIN_ACTION,
value: LOGIN_FAILURE,
};
}
};
export var logout_action = () => {
return {
type: LOGOUT_ACTION,
}
};
Reducers :
/* reducers/login.js */
import {LOGIN_ACTION, LOGOUT_ACTION, LOGIN_SUCCESS, LOGIN_FAILURE} from '../actions/actionTypes'
let cloneObject = (obj) => {
if(obj)
return JSON.parse(JSON.stringify(obj));
else
return {};
}
const initialState = {
user: {
loggedIn: false,
},
};
const auth = (state = initialState, action = {}) => {
switch(action.type) {
case LOGIN_ACTION: {
if(action.value === LOGIN_SUCCESS) {
return {
...state,
user: {
loggedIn: true
}
};
}else {
return {
...state,
user: {
loggedIn: false
}
};
}
}
case LOGOUT_ACTION: {
if(action.value === LOGIN_SUCCESS) {
return {
...state,
user: {
loggedIn: false
}
};
}else {
return state;
}
}
default: {
return state || initialState;
}
}
}
export default auth;
/* reducers/index.js */
import { combineReducers } from 'redux';
import auth from './login';
export {
auth,
};
Change onPress={this.onLoginPress} to onPress={this.onLoginPress.bind(this}.
Do the same for onPress={this.onLogoutPress} also.