I want to test Redux on my react-native app. I navigate through several Components - I want a component TestRedux updates a value and that another component TestRedux2 see this value using Redux.
I followed several tutorials on Redux and did this:
Actions:
//myApp/redux/Actions/action.js
import { ADD_RES } from "../Constants/action-types";
export function addResa(payload) {
return { type: ADD_RES, payload: payload };
}
Constants:
//myApp/redux/Components/action-types.js
export const ADD_RES = "ADD_RES";
export const DEL_RES = "DEL_RES";
Reducers:
//myApp/redux/Reducers/resaReducer.js
import { ADD_RES } from "../Constants/action-types";
const initialState = {
res: []
};
function resaReducer(state = initialState, action) {
let nextState;
switch (action.type) {
case ADD_RES:
nextState = {
...state,
payload: action.payload
}
return nextState;
default:
return state
}
}
export default resaReducer;
Store:
//myApp/redux/Store/store.js
import { createStore } from "redux";
import resaReducer from "../Reducers/resaReducer";
const Store = createStore(resaReducer);
export default Store;
TestRedux:
//myApp/redux/Components/TestRedux.js
// I use react-navigation to navigate between components. The component App is the first component and then trigger to testRedux
import React from 'react';
import { View, Text, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import { connect } from 'react-redux'
import { Provider } from 'react-redux'
import Store from '../Store/store'
import App from '../../App';
const mapStateToProps = (state) => {
return state.date
}
export class TestRedux extends React.Component {
render() {
this.props.dispatch(addResa(2));
return (
<View>
<Button
onPress={() => {this.props.navigation.navigate('TestRedux2')}}
title='test'
/>
<Provider store={Store}>
<App/>
</Provider>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux)
TestRedux2:
//myApp/redux/Components/TestRedux2.js
import { connect } from 'react-redux'
import React from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import Store from '../Store/store'
const mapStateToProps = (state) => {
return state.date
}
export class TestRedux2 extends React.Component {
render() {
console.log("Value from TestRedux2 is", Store.getState())
return (
<View>
<Text> Hello </Text>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux2)
Do I use correctly Redux ?
I have the following error: “Invariant Violation: Could not find "store" in the context of "Connect(TestRedux)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(TestRedux) in connect options.”
This code:
<Provider store={Store}>
<App/>
</Provider>
which is inside your TestRedux, should be inside your index.js file as follows:
render(
<Provider store={Store}>
<App/>
</Provider>,
document.getElementById('root')
)
import of course your store. That is assuming you haven't made any other changes in your initial index.js file.
Related
I try to test redux. I have component Main that redirects to TestRedux. TestRedux componenent make a dispatch and redirects to TestRedux2 when click on a button.
TestRedux and TestRedux2 are the same, I have made a copy/paste, just change values. TestRedux dispatch correctly, but testRedux2 trigger an error.
Do you know what is the cause of the problem ?
// TestRedux
import React from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import { connect } from 'react-redux'
import Store from '../Store/store'
const mapStateToProps = state => ({ date: state.date })
export class TestRedux extends React.Component {
render() {
this.props.dispatch(addResa(1));
return (
<View>
<Button
onPress={() => { this.props.navigation.navigate('TestRedux2') }}
title='test'
/>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux)
// TestRedux2
import React from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { ADD_RES } from "../Constants/action-types";
import {addResa} from "../Actions/actions";
import { connect } from 'react-redux'
import Store from '../Store/store'
const mapStateToProps = state => ({ date: state.date })
export class TestRedux2 extends React.Component {
render() {
this.props.dispatch(addResa(2));
return (
<View>
<Button
onPress={() => { this.props.navigation.navigate('TestRedux') }}
title='test2'
/>
</View>
)
}
}
export default connect(mapStateToProps)(TestRedux2)
Store is:
import { createStore } from "redux"; // without redux persist
import { persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import resaReducer from "../Reducers/resaReducer";
const rootPersistConfig = {
key: 'root',
storage: storage
}
const Store = createStore(persistCombineReducers(rootPersistConfig, {resaReducer}))
export default Store;
````
Dispatch an action or pass null to connect. See if the issue continues.
export default connect(mapStateToProps)(TestRedux2)
to
export default connect(mapStateToProps, null)(TestRedux2)
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) => {
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);
}
I am new to react-native and I am trying to implement a simple application using StackNavigation and react-redux with welcome and signup screens. I have configured both the screens using StackNavigation but for some reasons , only the SignUp screen pops up when the app starts. Below are my files :
Index.js
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyApp', () => App);
App.js
import React, { Component } from 'react';
import { Provider, connect } from "react-redux";
import { addNavigationHelpers } from "react-navigation";
import StackNavConfig from "./js/config/routes";
import getStore from "./js/store";
const AppNavigator = StackNavConfig;
const initialState = AppNavigator.router.getActionForPathAndParams('Welcome');
const navReducer = (state = initialState, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
const AppWithNavigationState = connect(state => ({
nav: state.nav,
}))(({ dispatch, nav }) => (
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })} />
));
const store = getStore(navReducer);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
}
export default App;
js/config/routes.js
import Welcome from "../components/Welcome/view/Welcome";
import SignUp from "../components/SignUp/view/SignUp";
import { StackNavigator } from "react-navigation";
const Routes = {
Welcome: { screen: Welcome , path: ''},
SignUp: { screen: SignUp , path : '/signup'},
};
const RoutesConfig = {
initialRouteName: 'Welcome',
headerMode: 'none',
};
export default StackNavConfig = StackNavigator(Routes, RoutesConfig);
store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import getRootReducer from "./reducers/index";
export default function getStore(navReducer) {
const store = createStore(
getRootReducer(navReducer),
undefined,
applyMiddleware(thunk)
);
return store;
}
Below are my components
Welcome.js
import React from 'react';
import {
View,
Image} from 'react-native';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as welcomeActions from "../actions/WelcomeActions";
import { welcomeStyles } from '../styles/WelcomeStyles';
class Welcome extends React.Component {
constructor(){
super();
this.state = { };
}
render(){
return (
<View style = {welcomeStyles.mainContainer}>
<Text>Welcome</Text>
</View>
);
}
}
export default connect(
state => ({
}),
dispatch => bindActionCreators(welcomeActions, dispatch)
)(Welcome);
SignUp.js
import React from 'react';
import {
View,
Image} from 'react-native';
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import * as welcomeActions from "../actions/SignUpActions";
import { signUpStyles } from '../styles/SignUpStyles';
class Welcome extends React.Component {
constructor(){
super();
this.state = { };
}
render(){
return (
<View style = {signUpStyles.mainContainer}>
<Text>SignUp</Text>
</View>
);
}
}
export default connect(
state => ({
}),
dispatch => bindActionCreators(signUpActions, dispatch)
)(SignUp);
I also have action and reducer files for each of my component.But they are blank as of now , since I haven't yet implemented the redux part.I am combining the reducers as below.
import { combineReducers } from "redux";
import welcomeReducer from "../components/Welcome/reducers/WelcomeReducer";
import signUpReducer from "../components/SignUp/reducers/SignUpReducer";
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
welcomeReducer : welcomeReducer,
signUpReducer : signUpReducer,
});
}
As mentioned before , even after setting the initialRouteName to Welcome in my routes.js , the SignUp screen appears first everytime I launch the app. Please help
I found out what was the issue. I was calling this.props.navigate inside the render function by mistake which was causing the navigation to different screen.
I'm playing with react-native / redux and am dispatching an action that is supposed to display a number yet an error gets thrown:
Unhandled JS Exception: Objects are not valid as a React child (found:
object with keys {type, payload}). If you meant to render a collection
of children, use an array instead or wrap the object using
createFragment(object) from the React add-ons. Check the render method
of Text.
createStore.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import createLogger from 'redux-logger';
import numReducer from './reducers/numReducer';
const logger = createLogger();
export default (initialState = {}) => (
createStore(
combineReducers({
numbers: numReducer
}),
initialState,
applyMiddleware(logger)
)
);
App.js
import React from 'react';
import { Provider } from 'react-redux';
import HomeScreen from './components/HomeScreen';
import createStore from './createStore';
const store = createStore();
export default () => (
<Provider store={store}>
<HomeScreen />
</Provider>
);
numReducer.js
import { LIST_NUMBERS, PICK_NUMBER } from '../actions/actionTypes';
export default (state = [], action = {}) => {
switch (action.type) {
case LIST_NUMBERS:
return action.payload || [];
case PICK_NUMBER:
return action.payload;
default:
return state;
}
};
HomeScreen.js
import React from 'react';
import { View } from 'react-native';
import NavContainer from '../containers/NavContainer';
const HomeScreen = () => (
<View>
<NavContainer />
</View>
);
export default HomeScreen;
NavContainer.js
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { listNumbers, pickNumber } from '../actions/numberActions';
import Nav from '../components/Nav';
const mapStateToProps = state => ({
numbers: state.numbers
});
const mapDispatchToProps = dispatch => (
bindActionCreators({
listNumbers,
pickNumber
}, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Nav);
Nav.js
import React, { Component, PropTypes } from 'react';
import { View, Text } from 'react-native';
export default class Nav extends Component {
render() {
return (
<View>
<Text>FirstLine</Text>
<Text>SecondLind</Text>
<Text>Number: {this.props.pickNumber(3)}</Text>
</View>
);
}
}
Please advise what I am doing wrong. Thank you
You need to dispatch your action from inside one of your lifecycle methods or on some handler, and then use the (updated) props from your redux store in your component.
Example:
import React, { Component, PropTypes } from 'react';
import { View, Text } from 'react-native';
export default class Nav extends Component {
componentDidMount() {
this.props.pickNumber(3);
}
render() {
return (
<View>
<Text>FirstLine</Text>
<Text>SecondLind</Text>
<Text>Number: {this.props.numbers}</Text>
</View>
);
}
}