Redux Saga - React Native - Proper setup? Actions issue? - react-native

I'm working with redux-saga for the first time and I'm not having luck with it at the moment. I'm thinking my actions aren't being passed into my saga, but I'm not sure?? Below I've provided a sample of the code. I'm currently not passing in any API calls, just some functions to get this going.
App.js File:
import React from "react";
import Setup from "./src/boot/setup";
import { Provider } from 'react-redux';
import store from './src/store';
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Setup/>
</Provider>
);
}
}
store.js
import {createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import AllReducers from '../src/reducers';
import rootSaga from '../src/saga';
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
AllReducers,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga);
export default store;
saga.js
import { call, put, takeEvery, takeLatest } from "redux-
saga/effects";
**import {receiveHelloWorld } from "./actions";
import { REQUEST_HELLO_WORLD } from "./actions/types";**
function* helloWorld(action) {
try {
yield put(receiveHelloWorld("Hello world from redux saga!"));
} catch (e) {
yield put(receiveHelloWorld("Hello world from redux saga!"));
}
}
export default function* rootSaga() {
yield takeLatest(REQUEST_HELLO_WORLD, helloWorld);
}
reducer.js
import { RECEIVE_HELLO_WORLD } from "../actions";
export default (state = "", { type, text = "" }) => {
switch (type) {
case RECEIVE_HELLO_WORLD:
return text;
default:
return state;
}
};
actionCreator.js (this is importing into the actions index.js file)
import { REQUEST_HELLO_WORLD, RECEIVE_HELLO_WORLD } from './types';
export const requestHelloWorld = () => ({
type: REQUEST_HELLO_WORLD
});
export const receiveHelloWorld = text => ({
type: RECEIVE_HELLO_WORLD, text
});
sagaScreen.js
import React, { Component } from "react";
import { Container, Text, Button } from "native-base";
import { connect } from "react-redux";
import styles from "../styles/styles";
import { bindActionCreators } from "redux";
import { requestHelloWorld } from "../actions";
class SagaScreen extends React.Component {
componentDidMount() {
this.props.requestHelloWorld();
}
render() {
return (
<Container style={styles.container}>
<Text style={{marginTop: 50 }}> {this.props.helloWorld} </Text>
</Container>
);
}
}
const mapStateToProps = state => ({ helloWorld: state.helloWorld });
const mapDispatchToProps = dispatch =>
bindActionCreators({ requestHelloWorld }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)
(SagaScreen);

Update Your saga:
saga.js
import { REQUEST_HELLO_WORLD, RECEIVE_HELLO_WORLD } from "./actions/types";
function* helloWorld(action) {
try {
yield put({type: RECEIVE_HELLO_WORLD, text: "Hello world from redux saga!"});
} catch (e) {
//Handling for error
}
}
export default function* watchStartSaga () {
yield takeLatest(REQUEST_HELLO_WORLD, helloWorld);
}
//Updated Answer
Create new file. index.js in directory src/saga.
index.js
import { fork } from "redux-saga/effects";
import watchStartSaga from "./saga";
export default function* rootSaga() {
yield fork(watchStartSaga);
}

Related

Possible import error after updating to SDK 33

I've upgraded to expo SDK 33 from SDK 32 and I am getting the following error:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite
components) but got: %s.%s%s, undefined, You likely forgot to export
your component from the file it's defined in, or you might have mixed
up default and named imports.
Check your code at App.js:117.,
My App.js looks like this(simplified)
import React, { Component } from 'react';
import { Alert, SafeAreaView } from 'react-native';
import { Root } from "native-base";
import {
StackNavigator,
addNavigationHelpers,
} from 'react-navigation';
import {
createStore,
applyMiddleware,
combineReducers,
compose
} from 'redux';
import {
createReduxBoundAddListener,
createReactNavigationReduxMiddleware,
} from 'react-navigation-redux-helpers';
import { Provider, connect } from 'react-redux';
import thunk from "redux-thunk";
import {
moveTo,
moveBack,
moveAndResetStack,
showSupport,
hideSupport,
startLoading,
stopLoading
} from './src/store/actions/index';
import { Notifications } from 'expo';
import { StyleProvider } from 'native-base';
import getTheme from './native-base-theme/components';
import platform from './native-base-theme/variables/platform';
// reducers
import routesReducer from './src/store/reducers/routes';
// Global headers
import AppNavigator from './AppNavigator';
import { State } from './src/modules/State';
import events from './src/events/events';
const navReducer = (state,action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
}
const appReducer = combineReducers({
...
});
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav,
);
const addListener = createReduxBoundAddListener("root");
class App extends Component {
constructor() {
super();
this.state = {
isReady: false,
}
}
async componentWillMount()
{
this.setState({isReady: true});
Notifications.addListener(this.handleNotification)
}
handleNotification(notification)
{
events.publish('displayNotification', notification);
}
render()
{
if (!this.state.isReady) {
return <Expo.AppLoading /> ;
}
return (
<Root>
<StyleProvider style={getTheme(platform)}>
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
addListener,
})} />
</StyleProvider>
</Root>
);
}
}
const mapStateToProps = (state) => ({
nav: state.nav
});
const AppWithNavigationState = connect(mapStateToProps, null)(App);
let composeEnhancers = compose;
if (__DEV__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const ww = createWw(appReducer, composeEnhancers(applyMiddleware(thunk, middleware)));
export default class Top extends Component {
render() {
return (
<Provider ww={ww}>
<AppWithNavigationState />
</Provider>
);
}
}
Have a look at file './src/store/actions/index' and './src/events/events'
It seems there are something not properly exported from there.

React Native and Redux Persist not saving state to persisted storage

I created a sample React Native app to see if I can get Redux-persist working. However my React Native app with Redux Persist is not saving state to persisted storage.
Every time i change the toggle to 'true' and then reload the app, the state is not persisted and goes back to null.
How can I get 'True' to stay persisted when I refresh the app.
Here is my code:
index.js:
import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
import React, {Component} from 'react';
import { Provider } from "react-redux";
import { store, persistor } from "./Store/index";
import { PersistGate } from 'redux-persist/integration/react'
import App from './App.js';
class ReduxPersistTest extends Component {
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<App />
</PersistGate>
</Provider>
);
}
}
AppRegistry.registerComponent('ReduxPersistTest', () => ReduxPersistTest);
store/index.js:
import { createStore, applyMiddleware, compose } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import toggle from "../Reducers/rootReducer.js";
import { AsyncStorage } from "react-native";
import {persistStore, persistReducer, persistCombineReducers} from "redux-persist";
import storage from 'redux-persist/lib/storage'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const togglePersistConfig = {
key: 'toggle',
storage: AsyncStorage
};
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage: AsyncStorage,
debug: true,
whitelist: ['toggle']
}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducers = { toggle: persistReducer(togglePersistConfig, toggle) };
const persistedReducer = persistCombineReducers(persistConfig, reducers);
export const store = createStore(
persistedReducer, composeEnhancers(applyMiddleware(...middleware))
);
export const persistor = persistStore(store);
Reducer.js
The issue might be found here in my reducer...
import { ADD_TOGGLE } from "../Constants/action-types";
import { combineReducers } from 'redux';
const initialState = {
toggle: false,
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TOGGLE:
console.log(action.payload.toggle);
console.log(action.payload);
return {
...state, toggle: {
toggle: action.payload.toggle,
}};
default:
return state;
}
};
export default rootReducer;
Actions/index.js
import { ADD_TOGGLE } from "../Constants/action-types";
export const addToggle = toggle => ({ type: ADD_TOGGLE, payload: toggle });
Constants/action-Types.js
export const ADD_TOGGLE = "ADD_TOGGLE";
And my components:
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Copy from './Components/Copy/Copy.js';
import CopyToggle from './Components/Copy/CopyToggle.js';
/*
Redux imports
*/
import { connect } from "react-redux";
import { addToggle } from "./Actions/index";
/*
Redux constants
*/
const mapDispatchToProps = dispatch => {
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
//Styles
const styles = StyleSheet.create({
textHeader: {
textAlign: 'center',
marginBottom: 10,
marginTop: 100,
},
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
toggle: false,
};
}
componentWillMount() {
const { toggle } = this.state;
this.props.addToggle({ toggle });
}
render() {
return (
<View>
<Text style={styles.textHeader}>Welcome to React Native!</Text>
<Copy />
<CopyToggle />
</View>
)
}
}
export default connect(null, mapDispatchToProps)(App);
Copy.JS (toggle UI to change the toggle value from 'true' to 'false'
import React, { Component } from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import { compose } from 'react-compose';
import { Switch } from 'react-native-switch';
import { connect } from "react-redux";
import { addToggle } from "../../Actions/index";
const mapDispatchToProps = dispatch => {
console.log('mapDispatchToProps hit');
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
class Copy extends Component {
constructor(props) {
super(props);
this.state = {
toggle: false,
};
this.addtoggle = this.addtoggle.bind(this);
}
addtoggle(val) {
this.setState({
toggle: val,
}, function () {
const { toggle } = this.state;
this.props.addToggle({ toggle });
});
}
render() {
return (
<View>
<Text>Test redux persist</Text>
<Switch
value={ this.state.toggle }
onValueChange={(val) => this.addtoggle(val)}
/>
</View>
);
}
}
export default connect(null, mapDispatchToProps)(Copy);
CopyToggle.js (outputs the boolean value of the toggle)
import React, { Component } from 'react';
/*
Redux imports
*/
import { connect } from "react-redux";
import { addToggle } from "../../Actions/index";
/*
Native base and react native
*/
import { StyleSheet, View, Text } from 'react-native';
/*
Redux constants
*/
const mapDispatchToProps = dispatch => {
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
const mapStateToProps = state => {
return { toggle: state.toggle.toggle };
};
// Custom Styles
const styles = StyleSheet.create({
textHeader: {
color: '#000',
},
});
//class
class CopyToggle extends Component {
constructor(props) {
super(props);
this.state = {
purchase: false,
};
this.toggleDisplay = this.toggleDisplay.bind(this);
}
componentWillMount() {
const { toggle } = this.state;
this.props.addToggle({ toggle });
}
//display to output bollean value
toggleDisplay() {
let toggleState;
if (this.props.toggle === false) {
toggleState = 'false'
}
else if (this.props.toggle === true) {
toggleState = 'true'
}
return (
<Text>{toggleState}</Text>
)
}
//render
render() {
return (
<View>
{this.toggleDisplay()}
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CopyToggle);
It would be greatly appreciated id someone with knowledge on Redux persist can review and hopefully point out my issue.
THanks!
You need to use persistCombineReducers if you want to persist just part of your store, so I would come up with something similar to this:
import { persistCombineReducers, persistReducer } from 'redux-persist';
import toggle from './Reducer.js';
const togglePersistConfig = {
key: 'toggle',
storage: AsyncStorage
};
const reducers = {
toggle: persistReducer(togglePersistConfig, toggle),
// ...other reducers
}
const persistConfig = {
key: 'root',
storage: AsyncStorage,
debug: true,
whitelist: ['toggle']
};
const persistedReducer = persistCombineReducers(persistConfig, reducers);
export const store = createStore( persistedReducer, composeEnhancers(applyMiddleware(...middleware)) );
You have to add the value you want to persist in the storage object:
AsyncStorage.setItem('toggle': this.state.toggle)

the global state won't update by dispatch reducer

index.js
import React from 'react';
import {
AppRegistry
} from 'react-native'
import App from './App';
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated', 'Module RCTImageLoader']);
AppRegistry.registerComponent('mhagora', () => App);
App.js
import React, { Component } from 'react';
import { Provider } from "react-redux";
import store from './app/store';
import { StyleProvider, getTheme } from "native-base";
import Setup from "./app/setup";
import variables from "./app/theme/variables/commonColor";
export default class App extends Component {
render() {
return (
<Provider store={store}>
<StyleProvider style={getTheme(variables)}>
<Setup />
</StyleProvider>
</Provider>
);
}
}
./app/setup.js
import React, { Component } from "react";
import axios from "axios/index";
import Config from "./config";
import { Root } from "native-base";
import AppNavigator from "./routes";
axios.defaults.baseURL = Config.API_BASE_URL;
axios.defaults.headers.common['Content-Type'] = Config.API_ACCEPT;
axios.defaults.headers.common['Accept'] = Config.API_ACCEPT;
axios.defaults.headers.common['secret'] = Config.API_SECRET;
export default class Setup extends Component {
render() {
return (
<Root>
<AppNavigator />
</Root>
);
}
}
./app/store/index.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import reducers from '../reducers';
const logger = createLogger();
export default createStore(reducers, compose(applyMiddleware(thunk, logger)));
./app/actions/index.js
import { APP_LOADING, APP_LOADED } from '../actionTypes';
export function appLoading() {
return (dispatch) => {
dispatch({type: APP_LOADING});
}
}
export function appLoaded() {
return (dispatch) => {
dispatch({type: APP_LOADED});
}
}
./app/actions/user.js
import { USER_LOADING, USER_LOADED, USER_FAILED, APP_LOADING, APP_LOADED } from "../actionTypes";
import axios from 'axios';
import Config from '../config';
export function userLogin(username, password) {
return (dispatch) => {
dispatch({type: USER_LOADING});
axios
.post("oauth/token", {
username: username,
password: password,
client_id: Config.API_CLIENT_ID,
client_secret: Config.API_CLIENT_SECRET,
grant_type: 'password',
}, {
headers: {}
})
.then(response => {
dispatch({
type: USER_LOADED,
data: response.data
});
})
.catch(err => {
dispatch({ type: USER_FAILED, error: err.response.data.message });
alert(err.response.data.message);
});
};
}
./app/reducers/index.js
import appReducer from './appReducer';
import userReducer from './userReducer';
import { combineReducers } from "redux";
const rootReducer = combineReducers({
appReducer,
userReducer
});
export default rootReducer;
./app/reducers/userReducer.js
import { USER_LOADING, USER_LOADED, USER_FAILED } from '../actionTypes';
const initialState = {
username: "",
password: "",
user: {}
};
export default userReducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOADING:
return Object.assign({}, state, {
loading: true,
user: {},
});
case USER_LOADED:
return Object.assign({}, state, {
loading: false,
user: action.data
});
case USER_FAILED:
return Object.assign({}, state, {
loading: false,
});
default:
return state
}
}
./app/reducers/appReducer.js
import { APP_LOADING, APP_LOADED } from "../actionTypes";
const initialState = {
loading: true,
};
export default appReducer = (state = initialState, action) => {
switch (action.type) {
case APP_LOADING:
return Object.assign({}, state, {
loading: true
});
case APP_LOADED:
return Object.assign({}, state, {
loading: false
});
default:
return state;
}
};
./app/screens/home.js
'use strict';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { SkypeIndicator } from 'react-native-indicators';
import * as Actions from '../actions/index';
import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon, Text, View } from 'native-base';
class HomeScreen extends Component {
componentDidMount() {
/** HERE, the apps should show a loading page forever but it didn't **/
// setTimeout( _ => {
// this.props.appLoaded();
// }, 2000);
}
render() {
if (this.props.loading) {
return (
<SkypeIndicator />
);
} else {
return (
<Container>
<Header>
</Header>
<Body>
<Button
onPress={() =>
this.props.navigation.navigate('LoginScreen')
}><Text>Login now</Text></Button>
<Text>Hello</Text>
</Body>
</Container>
);
}
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);
./app/screens/loginScreen
'use strict';
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { SkypeIndicator } from 'react-native-indicators';
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { Body, Button, Container, Content, Header, Icon, Left, Text, Title, View } from "native-base";
import t from 'tcomb-form-native';
import { LoginUserModel, LoginUserModelOption } from "../models/UserModel";
import styles from '../styles';
import LoadingButton from 'react-native-loading-button';
import * as UserActions from '../actions/user';
const Form = t.form.Form;
const ps = StyleSheet.create({
...styles,
container: {
justifyContent: 'center',
marginTop: 50,
padding: 20
},
});
class LoginScreen extends Component {
constructor(props) {
super(props);
}
onSubmitHandler = () => {
const value = this._form.getValue();
if(value) {
this.props.userLogin(value.username, value.password);
}
};
render() {
return (
<Container>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="arrow-back"/>
</Button>
</Left>
<Body>
<Title>Headers</Title>
</Body>
</Header>
<Content padder>
<View style={ps.container}>
<Form ref={c => this._form = c} type={LoginUserModel} options={LoginUserModelOption} />
<LoadingButton
block
onPress={this.onSubmitHandler.bind(this)}
isLoading={this.props.loading}
style={{ justifyContent: 'center' }}
><Icon name="checkmark"/><Text>Login Now</Text></LoadingButton>
</View>
</Content>
</Container>
);
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
the homeScreen should result in a forever loading page but it didn't
the loginScreen button should automatically loading when pressing, but it didn't
new to react-native, i have tried to set/play with the state/props but it just seems like not changing/connected, i also have another page trying to check the state is synced but results is like always get the fresh state, as my understanding the state is something like GLOBAL variable accessible in any component connect to redux
MY QUESTION IS
1. is the react-native/redux/redux-thunk setup correctly? if not, where is the error
2. is the state/props is global accessible in any component that connect with redux
3. if statement 2 is correct, what the different between state/props? this.state and this.props
4. i don't really understand the promise work, how can we handle / wait untill the api call complete(success/error) before move to next step/flow, i use php a lot and my logic is stuck at each function should return something then depends on the results process to next function...and then...
your answer / precious time spend for reading this question is appreciated, thank you
created a github for easy to reproduce/test
https://github.com/weiloon1234/react-native-test

Redux-Saga Component Setup

I'm having trouble getting redux-saga to work. I'm thinking the issue lies somewhere between the saga_component.js and saga_screen.js files. It may be I'm not using the correct syntax to map out the API?
I'm getting eror:
"undefined is not a function (near '...ConnectData.map' ".
This is located in the Saga_component.js file.
I've been working on this for a while now, not sure what to adjust at this point. Would greatly appreciate some guidance. This is a link to the repo. All screens and components can be found in the 'src' file.
App.js File
import React from "react";
import Setup from "./src/boot/setup";
import { Provider } from 'react-redux';
import store from './src/store';
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Setup/>
</Provider>
);
}}
Store.js
import {createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import AllReducers from '../src/reducers';
import rootSaga from '../src/saga';
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
AllReducers,
applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export default store;
saga.js
import { call, put, takeEvery, takeLatest } from "redux-saga/effects";
import { REQUEST_API_DATA, receiveApiData } from "./actions";
import { fetchData } from "./api";
function* getApiData(action) {
try {
// do api call
const data = yield call(fetchData);
yield put(receiveApiData(data));
} catch (e) {
console.log(e);
}
}
export default function* rootSaga() {
yield takeLatest(REQUEST_API_DATA, getApiData);
}
data.js (this is the reducer)
import { RECEIVE_API_DATA } from "../actions";
export default (state = {}, { type, data }) => {
switch (type) {
case RECEIVE_API_DATA:
return data;
default:
return state;
}
};
actionsCreators.js
import { REQUEST_API_DATA, RECEIVE_API_DATA} from './types';
export const requestApiData = () => {
return {
type: REQUEST_API_DATA
}
};
export const receiveApiData = (data) => {
return {
type: RECEIVE_API_DATA,
data
}
};
saga_component.js
import React from "react";
import { AppRegistry, View, StatusBar } from "react-native";
import { Container, Body, Content, Header, Left, Right, Icon, Title,
Input, Item, Label, Button, Text } from "native-base";
export default class SagaComponent extends React.Component {
renderList() {
const ConnectData = this.props.data;
return ConnectData.map((data) => {
return (
<View style={{width: 280}}>
<Text style={styles.TextLight}><Text style={styles.TextDark}>Dest City:</Text> {data.name}</Text>
<Text style={styles.TextLight}><Text style={styles.TextDark}>ETA:</Text> {data.email}</Text>
</View>
);
});
}
render() {
return (
<View>
<Label>Username</Label>
{this.renderList()}
</View>
);
}
}
saga_screen.js
import React, { Component } from "react";
import { Container, Text, Button } from "native-base";
import { View, StatusBar } from "react-native";
import { connect } from "react-redux";
import styles from "../styles/styles";
import { bindActionCreators } from "redux";
import { requestApiData } from "../actions";
import SagaComponent from '../components/saga_component';
class SagaScreen extends React.Component {
render() {
return (
<Container style={styles.container}>
<View style={{marginTop: 50 }}>
<SagaComponent data={this.props.data}/>
</View>
<Button block style={styles.Home_btns}
onPress={() => this.props.navigation.navigate("Home")}>
<Text>Home</Text>
</Button>
</Container>
);
}
}
function mapStateToProps(state) {
return {
data: state.data,
};
}
const mapDispatchToProps = dispatch =>
bindActionCreators({ requestApiData }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(SagaScreen);
Api.js
export const fetchData = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
return data;
} catch (e) {
console.log(e);
}
};
index.js(reducer index.js file)
import { combineReducers } from 'redux';
import data from "./data";
const AllReducers = combineReducers({
data,
});
export default AllReducers;
It looks like the problem could be in your reducer. Instead of returning data you should return { data };
Also, as an aside, you might want to guard against falsey data in your saga_component (ConnectData || []).map((data) => {

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.