how to mock saga generator function with jest and enzyme - testing

I'm trying to test if my generator function is called when I call dispatch function. I'm doing something like:
in my saga.js:
import { takeLatest } from 'redux-saga/effects'
import { SIGNIN_FORM_SUBMIT } from './constants'
export default function* signInFormSubmit() {
return yield takeLatest([SIGNIN_FORM_SUBMIT], function*() {
console.log("I'm in")
return yield null
})
}
in Form.js:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { SIGNIN_FORM_SUBMIT } from '../constants'
class SignInForm extends Component {
handleSubmit = this.handleSubmit.bind(this)
handleSubmit() {
this.props.dispatch({
type: SIGNIN_FORM_SUBMIT
})
}
render() {
return (
<div>
<button onClick={() => this.handleSubmit()}>Submit</button>
</div>
)
}
}
export default connect()(SignInForm)
in my test.js:
import React from 'react'
import configureStore from 'redux-mock-store'
import createSagaMiddleware from 'redux-saga'
import { Provider } from 'react-redux'
import { mount } from 'enzyme'
import signInFormSubmitSaga from '../saga'
import SignInForm from '../components/Form'
const sagaMiddleware = createSagaMiddleware()
const mockStore = configureStore([sagaMiddleware])
const store = mockStore()
sagaMiddleware.run(signInFormSubmitSaga)
describe('<Form />', () => {
test('should call signInFormSubmit saga when I click on the sumbit button', () => {
const wrapper = mount(
<Provider store={store}>
<SignInForm />
</Provider>
)
wrapper.find('button')
.simulate('click')
// I'm blocked here
// expect(...).toHaveBeenCalledTimes(1)
}
})
I think my problem is that I have to mock the generator function that I pass as params to takeLatest. I tried lot of things but it didn't work.
The result show well "I'm in"
Thanks guys for your help :)

check out this link to see example test of a saga: https://github.com/react-boilerplate/react-boilerplate/blob/master/app/containers/HomePage/tests/saga.test.js
What you want to do is test the saga generator function, in your case the signInFormSubmit...
You would import it as such as:
signInFormSubmitGenerator = signInFormSubmit();
Then you would want to assert things against the values returned at each 'yield' step.
However, I noticed that you have takeLatest within your generator function, which I don't believe is correct way to use it, its used to trigger other sagas (so it should be outside of the function, take a look here for example of well structured saga: https://github.com/react-boilerplate/react-boilerplate/blob/master/app/containers/HomePage/saga.js, notice how takeLatest is all the way at the bottom, and used as an entry point or a trigger for the generator function above it)

Related

Redux: mapStateToProps is not being called

I understand this kind of question was already asked several times here at StackOverflow. But I tried all the recommended solutions and nothing works for me. I'm running out of ideas.
The problem is with a React Native application for Android. Basically, the app provides a search bar to search an underlying database. The search results should be put into the store.
I use Redux v4.0.5, React-Redux v7.1.3, React v16.12.0 and React Native v0.61.5. For debugging, I use React Native Debugger in the latest version.
Now the simplified code. First, the component with the search bar. Here, mapStateToProps() is called. User makes an input and useEffect() immediately runs the database query, which should result in immediately calling mapStateToProps().
import React, {useEffect, useRef, useState} from 'react';
import {connect} from 'react-redux';
import {RootState} from '../../../rootReducer/rootReducer';
import {setResultValueSearchBar} from '../../../store/searchBar/actions';
imports ...
type Props = {};
const SearchBar: React.FC<Props> = () => {
const [returnValue, setReturnValue] = useState('');
const [inputValue, setInputValue] = useState('');
useEffect(() => {
// get query results
// logic to finally get a result string that should be put into the store
const resultNames: string = resultNamesArray.toString();
// method to set local and Redux state
const sendReturnValueToReduxStore = (resultNames: string) => {
setReturnValue(resultNames);
setResultValueSearchBar({resultValue: resultNames});
console.log('result value sent to store ', resultNames);
};
// call above method
sendReturnValueToReduxStore(resultNames);
}, [inputValue, returnValue]);
return (
<View>
<ScrollView>
<Header searchBar>
<Item>
<Input
placeholder="Search"
onChangeText={text => setInputValue(text)}
value={inputValue}
/>
</Item>
</Header>
</ScrollView>
</View>
);
};
function mapStateToProps(state: RootState) {
console.log("map state to props!", state); // is only called one time, initially
return {
resultValue: state.searchBarResult.resultValue,
};
}
const mapDispatchToProps = {
setResultValueSearchBar,
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Here is the rootReducer:
import {combineReducers} from 'redux';
import searchBarResultReducer from '../store/searchBar/reducers';
import reducer2 from '../store/reducer2example/reducers';
const rootReducer = combineReducers({
searchBarResult: searchBarResultReducer,
reducer2Result: reducer2,
});
export type RootState = ReturnType<typeof rootReducer>;
Here is the searchBarResultReducer in reducers.ts file:
import {
SearchBarResultState,
SET_RESULT_VALUE_SEARCHBAR,
ResultValueType,
} from './types';
const initialState: SearchBarResultState = {
resultValue: 'No results',
};
// take state and action and then return a new state
function searchBarResultReducer(
state = initialState,
action: ResultValueType,
): SearchBarResultState {
console.log('invoked result: ', action.type); // called only initially
if (action.type === 'SET_RESULT_VALUE_SEARCHBAR') {
return {
...state,
...action.payload,
};
} else {
return state;
}
}
export default searchBarResultReducer;
And the corresponding types.ts ...
export const SET_RESULT_VALUE_SEARCHBAR = 'SET_RESULT_VALUE_SEARCHBAR';
export interface SearchBarResultState {
resultValue: string;
}
interface ResultValueAction {
type: typeof SET_RESULT_VALUE_SEARCHBAR;
payload: SearchBarResultState;
}
export type ResultValueType = ResultValueAction
... and the actions.ts:
import {SET_RESULT_VALUE_SEARCHBAR, ResultValueType, SearchBarResultState} from './types'
export const setResultValueSearchBar = (resultValue: SearchBarResultState): ResultValueType => ({
type: SET_RESULT_VALUE_SEARCHBAR,
payload: resultValue,
});
And index.js:
import React from 'react';
import {AppRegistry} from 'react-native';
import {createStore, applyMiddleware, compose} from 'redux';
import {Provider} from 'react-redux';
import App from './App';
import {name as appName} from './app.json';
import rootReducer from './src/rootReducer/rootReducer';
import Realm from 'realm';
import { composeWithDevTools } from 'redux-devtools-extension';
import invariant from 'redux-immutable-state-invariant';
const composeEnhancers = composeWithDevTools({});
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(invariant()))
);
const Root = () => {
Realm.copyBundledRealmFiles();
return (
<Provider store={store}>
<App />
</Provider>
);
};
AppRegistry.registerComponent(appName, () => Root);
To summarize: Whenever the database query succeeds, the result value should be sent to the store. But in the React Native Debugger/Redux Devtools, the reducer/mapStateToProps() is called only once and only, as shown by the console.log s in the code.
What is going on here?
Solved! As stated by Hemant in this Thread, you also have to pass the action that you import as props into the component. Works like a charm now :)

Async call with react native and redux , thunk

I have been following this tutorial to integrate redux into my react native app.
https://github.com/jlebensold/peckish
On my Home view, I'm not able to call the functions from my action folder.
One difference is that I'm using react-navigation in my app. Wonder if I need to integrate redux with react navigation to be able to use redux for all data?
Below is the full implementation code I have been doing.
On the Home screen, I call the fetchSite function on ComponentDidMount to launch an async call with axios. But I can't even access to this function.
Sorry for this long post but I can't figure out how to make this work so quite difficult to make a shorter code sample to explain the structure of my app.
Let me know if any question.
index.ios.js
import React from 'react'
import { AppRegistry } from 'react-native'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose} from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'
import reducer from './app/reducers'
import AppContainer from './app/index'
// middleware that logs actions
const loggerMiddleware = createLogger({ predicate: (getState, action) => __DEV__ });
function configureStore(initialState) {
const enhancer = compose(
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware,
),
);
return createStore(reducer, initialState, enhancer);
}
const store = configureStore({});
const App = () => (
<Provider store={store}>
<AppContainer />
</Provider>
);
AppRegistry.registerComponent('Appero', () => App;
reducers/index.js
import { combineReducers } from 'redux';
import * as sitesReducer from './sites'
export default combineReducers(Object.assign(
sitesReducer,
));
reducers/sites.js
import createReducer from '../lib/createReducer'
import * as types from '../actions/types'
export const searchedSites = createReducer({}, {
[types.SET_SEARCHED_SITES](state, action) {
let newState = {};
action.sites.forEach( (site) => {
let id = site.id;
newState[id] = Object.assign({}, site, { id });
});
return newState;
},
});
../lib/createReducer
export default function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
../actions/types
export const SET_SEARCHED_SITES = 'SET_SEARCHED_SITES';
AppContainer in ./app/index
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ActionCreators } from './actions';
console.log(ActionCreators); //Properly gathered the functions from the actions folder
import { Root } from './config/router';
window.store = require('react-native-simple-store');
window.axios = require('axios');
class App extends Component {
render() {
return (
<Root />
)
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(ActionCreators, dispatch);
}
export default connect(mapDispatchToProps)(App);
ActionCreators in './actions';
import * as SiteActions from './sites'
export const ActionCreators = Object.assign({},
SiteActions,
);
Actions in './actions/sites'
import * as types from './types' //See above
export function fetchSites(token) {
return (dispatch, getState) => {
let instance = axios.create({
baseURL: url + 'api/',
timeout: 10000,
headers: {'Accept' : 'application/json', 'Authorization' : 'Bearer ' + token}
});
instance.get('/sites?page=1')
.then(response => {
console.log(response.data.data);
dispatch(setSearchedSites({sites: response.data.data}));
}).catch(error => {
console.log(error);
});
}
}
export function setSearchedSites({ sites }) {
return {
type: types.SET_SEARCHED_SITES,
sites,
}
}
Root file for navigation based on react-navigation
I made it as simple as possible for this example.
import React from 'react';
import {StackNavigator} from 'react-navigation';
import Home from '../screens/Home';
export const Root = StackNavigator({
Home: {
screen: Home,
}
});
And finally my Home screen
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {Text, View} from 'react-native';
class Home extends Component {
componentDidMount()
{
let token = "12345678" //Just for this example
this.props.fetchSites(token).then( (response) => {
console.log(response);
});
}
render() {
return (
<View>
<Text>This is the Home view</text>
</View>
);
}
}
function mapStateToProps(state) {
return {
searchedSites: state.searchedSites
};
}
export default connect(mapStateToProps)(Home);
To use action methods you need to connect in home screen like this
import { fetchSites } from '<your-path>'
// your Home's other code.
const mapDispatchToProps = (dispatch) => {
return{
fetchSites:dispatch(fetchSites())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Home);
after that you can use fetchSites as this.props.fetchSites whenever you want.

How to listen to route changes in react router v4?

I have a couple of buttons that acts as routes. Everytime the route is changed, I want to make sure the button that is active changes.
Is there a way to listen to route changes in react router v4?
I use withRouter to get the location prop. When the component is updated because of a new route, I check if the value changed:
#withRouter
class App extends React.Component {
static propTypes = {
location: React.PropTypes.object.isRequired
}
// ...
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
this.onRouteChanged();
}
}
onRouteChanged() {
console.log("ROUTE CHANGED");
}
// ...
render(){
return <Switch>
<Route path="/" exact component={HomePage} />
<Route path="/checkout" component={CheckoutPage} />
<Route path="/success" component={SuccessPage} />
// ...
<Route component={NotFound} />
</Switch>
}
}
To expand on the above, you will need to get at the history object. If you are using BrowserRouter, you can import withRouter and wrap your component with a higher-order component (HoC) in order to have access via props to the history object's properties and functions.
import { withRouter } from 'react-router-dom';
const myComponent = ({ history }) => {
history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state)
});
return <div>...</div>;
};
export default withRouter(myComponent);
The only thing to be aware of is that withRouter and most other ways to access the history seem to pollute the props as they de-structure the object into it.
As others have said, this has been superseded by the hooks exposed by react router and it has a memory leak. If you are registering listeners in a functional component you should be doing so via useEffect and unregistering them in the return of that function.
v5.1 introduces the useful hook useLocation
https://reacttraining.com/blog/react-router-v5-1/#uselocation
import { Switch, useLocation } from 'react-router-dom'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
You should to use history v4 lib.
Example from there
history.listen((location, action) => {
console.log(`The current URL is ${location.pathname}${location.search}${location.hash}`)
console.log(`The last navigation action was ${action}`)
})
withRouter, history.listen, and useEffect (React Hooks) works quite nicely together:
import React, { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
const Component = ({ history }) => {
useEffect(() => history.listen(() => {
// do something on route change
// for my example, close a drawer
}), [])
//...
}
export default withRouter(Component)
The listener callback will fire any time a route is changed, and the return for history.listen is a shutdown handler that plays nicely with useEffect.
import React, { useEffect } from 'react';
import { useLocation } from 'react-router';
function MyApp() {
const location = useLocation();
useEffect(() => {
console.log('route has been changed');
...your code
},[location.pathname]);
}
with hooks
With hooks:
import { useEffect } from 'react'
import { withRouter } from 'react-router-dom'
import { history as historyShape } from 'react-router-prop-types'
const DebugHistory = ({ history }) => {
useEffect(() => {
console.log('> Router', history.action, history.location)
}, [history.location.key])
return null
}
DebugHistory.propTypes = { history: historyShape }
export default withRouter(DebugHistory)
Import and render as <DebugHistory> component
import { useHistory } from 'react-router-dom';
const Scroll = () => {
const history = useHistory();
useEffect(() => {
window.scrollTo(0, 0);
}, [history.location.pathname]);
return null;
}
With react Hooks, I am using useEffect
import React from 'react'
const history = useHistory()
const queryString = require('query-string')
const parsed = queryString.parse(location.search)
const [search, setSearch] = useState(parsed.search ? parsed.search : '')
useEffect(() => {
const parsedSearch = parsed.search ? parsed.search : ''
if (parsedSearch !== search) {
// do some action! The route Changed!
}
}, [location.search])
in this example, Im scrolling up when the route change:
import React from 'react'
import { useLocation } from 'react-router-dom'
const ScrollToTop = () => {
const location = useLocation()
React.useEffect(() => {
window.scrollTo(0, 0)
}, [location.key])
return null
}
export default ScrollToTop
In some cases you might use render attribute instead of component, in this way:
class App extends React.Component {
constructor (props) {
super(props);
}
onRouteChange (pageId) {
console.log(pageId);
}
render () {
return <Switch>
<Route path="/" exact render={(props) => {
this.onRouteChange('home');
return <HomePage {...props} />;
}} />
<Route path="/checkout" exact render={(props) => {
this.onRouteChange('checkout');
return <CheckoutPage {...props} />;
}} />
</Switch>
}
}
Notice that if you change state in onRouteChange method, this could cause 'Maximum update depth exceeded' error.
For functional components try useEffect with props.location.
import React, {useEffect} from 'react';
const SampleComponent = (props) => {
useEffect(() => {
console.log(props.location);
}, [props.location]);
}
export default SampleComponent;
For React Router v6 & React Hooks,
You need to use useLocation instead of useHistory as it is deprecated
import { useLocation } from 'react-router-dom'
import { useEffect } from 'react'
export default function Component() {
const history = useLocation();
useEffect(() => {
console.log('> Router', history.pathname)
}, [history.pathname]);
}
With the useEffect hook it's possible to detect route changes without adding a listener.
import React, { useEffect } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import Main from './Main';
import Blog from './Blog';
const App = ({history}) => {
useEffect( () => {
// When route changes, history.location.pathname changes as well
// And the code will execute after this line
}, [history.location.pathname]);
return (<Switch>
<Route exact path = '/' component = {Main}/>
<Route exact path = '/blog' component = {Blog}/>
</Switch>);
}
export default withRouter(App);
I just dealt with this problem, so I'll add my solution as a supplement on other answers given.
The problem here is that useEffect doesn't really work as you would want it to, since the call only gets triggered after the first render so there is an unwanted delay.
If you use some state manager like redux, chances are that you will get a flicker on the screen because of lingering state in the store.
What you really want is to use useLayoutEffect since this gets triggered immediately.
So I wrote a small utility function that I put in the same directory as my router:
export const callApis = (fn, path) => {
useLayoutEffect(() => {
fn();
}, [path]);
};
Which I call from within the component HOC like this:
callApis(() => getTopicById({topicId}), path);
path is the prop that gets passed in the match object when using withRouter.
I'm not really in favour of listening / unlistening manually on history.
That's just imo.

Actions may not have an undefined "type" property - React-Native/Redux

I am receiving the Actions may not have an undefined "type" property. Have you misspelled a constant? error from all actions within the RecommendationItemActions.js actions file.
This issue occurs with both exports contained within.
import firebase from 'firebase';
import {} from 'redux-thunk';
import {
RECOMMENDATION_ITEM_UPDATE,
RECOMMENDATION_ITEMS_FETCH_SUCCESS
} from './types';
export const recommendationItemUpdate = ({ prop, value }) => {
//ex. Want to change name? Send in prop [name] and value [ what you want the new name to be ]
return {
type: RECOMMENDATION_ITEM_UPDATE,
payload: { prop, value }
};
};
export const recommendationItemsFetch = () => {
const { currentUser } = firebase.auth();
return (dispatch) => {
firebase.database().ref(`/users/${currentUser.uid}/recommendationItems`)
//snapshot is the value found
//snapshot is an object that describes what data is there
.on('value', snapshot => {
console.log(snapshot.val());
dispatch({
type: RECOMMENDATION_ITEMS_FETCH_SUCCESS,
payload: snapshot.val()
});
//snapshot.val() is the actual data at snapshot
});
};
};
These types are clearly defined within the imported type.js file
export const RECOMMENDATION_ITEMS_FETCH_SUCCESS = 'recommendation_items_fetch_success';
export const RECOMMENDATION_ITEM_UPDATE = 'recommendation_item_update';
The Actions are exported via the index.js file in the actions folder
export * from './RecommendationItemActions';
They are imported within the RecommendationItemCreate.js file
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Text, Image, View } from 'react-native';
import { recommendationItemUpdate, recommendationItemCreate } from '../../actions';
import { CardSection, Input, Button } from './../common';
import GooglePlacesAutocomplete from './../googlePlaces/GooglePlacesAutocomplete';
And referenced within the onChangeText of my Input component
<Input
label="ADDRESS"
placeholder="Address"
value={this.props.formattedAddress}
onChangeText={
value => this.props.recommendationItemUpdate({ prop: 'formattedAddress', value })
}
/>
I can not trace any error that would lead to the Actions may not have an undefined "type" property and have been banging my head against this for a couple days now. The type is clearly defined and can be accurately traced throughout it's usage.
Closest Reference Found
React+Redux - Uncaught Error: Actions may not have an undefined “type” property
This was still unhelpful. Here is my createStore as reference:
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import firebase from 'firebase';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers';
import Router from './Router';
class App extends Component {
componentWillMount() {
const config = {
REMOVED FOR EXAMPLE
};
firebase.initializeApp(config);
}
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
//applyMiddleware is a store-enhancer - it adds additional functionality
//to store
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
export default App;
I believe I have hit an issue like this before, and it was caused by dispatching a plain object within a thunk rather than an action creator, such as what you are doing. Could you try creating a recommendationItemsFetchSuccess action creator and dispatching that? Such as:
const recommendationItemsFetchSuccess = (snapshotVal) => {
return {
type: RECOMMENDATION_ITEMS_FETCH_SUCCESS,
payload: snapshotVal
};
}
export const recommendationItemsFetch = () => {
const { currentUser } = firebase.auth();
return (dispatch) => {
firebase.database().ref(`/users/${currentUser.uid}/recommendationItems`)
.on('value', snapshot => {
dispatch(recommendationItemsFetchSuccess(snapshot.val()));
});
};
};
As stated by DJSrA the fix is a reference error. In my case, I was importing from two different files that contains constants. I referenced the wrong file for a particular constant.

Getting "Actions may not have an undefined "type""

I'm getting the error:
Actions may not have an undefined "type" property.
But I'm sure I defined it and spelled it right.
App:
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { AsyncStorage } from 'react-native';
import thunk from 'redux-thunk';
import {persistStore, autoRehydrate} from 'redux-persist';
import FBLoginView from '../components/FBLoginView'
import * as reducers from '../reducers';
import Routing from './Routing';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer, undefined, autoRehydrate());
persistStore(store, {
storage: AsyncStorage,
}, () => {
})
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Routing />
</Provider>
);
}
}
Actions:
import * as types from './actionTypes';
export function getFacebookUser(user) {
return {
type: types.GET_FACEBOOK_USER,
user: user,
};
}
Types:
export const GET_FACEBOOK_USER = 'GET_FACEBOOK_USER';
Reducer:
import * as types from '../actions/actionTypes';
const initialState = {
user: {},
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.GET_FACEBOOK_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}
Edit (My home.js page)
import React, { Component } from 'react'
import { View, Text, StyleSheet, Image, TouchableHighlight } from 'react-native'
import { Actions } from 'react-native-router-flux'
import {FBLogin, FBLoginManager} from 'react-native-facebook-login'
import FBLoginView from '../components/FBLoginView'
import * as facebookActions from '../actions/facebookActions';
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
login: false
};
console.log(this.props)
}
render() {
let { facebook, actions } = this.props
_onLogin = (e) => {
actions.getFacebookUser(e.profile)
console.log(facebook)
}
_onLogout = (e) => {
console.log(e)
}
return (
<View style={styles.background}>
<Text>{this.state.login ? "Logged in" : "Logged out"}</Text>
<FBLogin
buttonView={<FBLoginView />}
ref={(fbLogin) => { this.fbLogin = fbLogin }}
loginBehavior={FBLoginManager.LoginBehaviors.Native}
permissions={["email","user_friends"]}
onLogin={function(e){_onLogin(e)}}
onLoginFound={function (e){console.log(e)}}
onLoginNotFound={function(e){console.log(e)}}
onLogout={function(e){_onLogin(e)}}
onCancel={function(e){console.log(e)}}
onError={function(e){console.log(e)}}
onPermissionsMissing={function(e){console.log(e)}}
style={styles.fbButton}
passProps={true}
/>
</View>
)
}
}
export default connect(store => ({
facebook: store.facebook.user,
}),
(dispatch) => ({
actions: bindActionCreators(facebookActions, dispatch)
})
)(Home);
const styles = StyleSheet.create({
background: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00796B',
},
});
I don't think that you're dispatching the action correctly:
actions.getFacebookUser(e.profile)
is an action creator and will just return the action, not dispatch it.
I can't see your Home component that you're hooking up with Connect but I'd guess this is the source of events that you will want to dispatch as actions. Why not try dispatching directly against the store, and then move to use connect to hook up with mapDispatchToProps? Finally you can use bindActionCreators if this is necessary.
There are two very good (free) egghead.io courses that will help here, both by Dan Abramov:
https://egghead.io/courses/getting-started-with-redux
https://egghead.io/courses/building-react-applications-with-idiomatic-redux
and the docs are also very good, but I guess you've seen them.
After seeing more of the code, I can't see how the component you're connecting (Home) is linking its events (for example onLogin) to a dispatch property. I can see it caling its own internal function called _onLogin, but this just in turn call the action creator, it won't dispatch.
The connect function allows you connect properties on a component (here, Home) with the redux store; it effectively links, in your example, the 'onLogin' property of your Home component with a particular action and can then dispatch that action to the store.
So,your Home component needs to accept a property like 'onLogin' that it can then call; mapDispatchToProps is a function you write to marry up your child component's properties to dispatch actions. bindActionCreators is just a further helper to bind to action creators; it may be overkill in your current use case.
Dan Abramov explains this so much better than I can, so see the docs, but also see his answer here:
How to get simple dispatch from this.props using connect w/ Redux?