I'm trying to get data from App.js for network connection availability. I'm getting data from App.js to action and reducer but the reducer is not updating the state for my component. The console log in the reducer is working but I'm not able to get data in the mapStateToProps of myComponent.
My App.js file contains this code.
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { NetInfo } from 'react-native';
import { createStore, applyMiddleware } from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './src/reducers';
import Router from './src/Router';
import { internetConnectionChanged } from './src/actions/';
class App extends Component {
componentWillMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange);
}
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
store.dispatch(internetConnectionChanged(isConnecteds));
});
};
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<Router />
</Provider>
);
}
}
export default App;
My code in action file is
import { CONNECTION_CHANGE } from '../actions/types';
export const internetConnectionChanged = (isConnected) => {
return {
type: CONNECTION_CHANGE,
payload: isConnected
};
};
That is exported via the index.js of actions file
through export * from './AppActions';
Code for the reducer is
import { CONNECTION_CHANGE } from '../actions/types';
const INITIAL_STATE = { isConnected: false };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CONNECTION_CHANGE:
console.log(action.payload);
return { ...state, isConnected: action.payload };
default:
return state;
}
};
Under my component, this is the code to get the info is
const mapStateToProps = ({ auth, app }) => {
const { email, password, error, loading } = auth;
const { isConnected } = app;
return { email, password, error, loading, isConnected };
};
export default connect(mapStateToProps, {
emailChanged,
passwordChanged,
loginUser,
forgotPasswordAction,
})(LoginForm);
Create store outside the App class. This might be causing the store to always have initial reducer values. Just paste the below line before Class App extends Component line
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Also remove the same above line of code from the following function
handleConnectionChange = (isConnected) => {
NetInfo.isConnected.fetch().done(
(isConnecteds) => {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk)); //remove this line
store.dispatch(internetConnectionChanged(isConnecteds));
});
};
Related
Hi i am new to react native and redux.
I am using redux persist to store data locally, but there are some keys that i don't want to persist. For that i am using blacklist, which is not working. It persist all the keys and not ignoring the keys that i want. Here is the code
I dont want to persist data and tabbar.
store.js
import rootReducer from './reducers'
// import AsyncStorage from '#react-native-community/async-storage';
import { AsyncStorage } from 'react-native';
import { persistStore, persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
let store = createStore(persistedReducer)
let persistor = persistStore(store)
export { store, persistor }.
reducers/product.js
const initialState = {
products: [],
favs: [],
data: [],
tabbar: false
}
const products = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS:
return {
...state,
products: action.products
}
case ADD_TO_FAV:
return {
...state,
favs: [...state.favs, action.product]
}
case REMOVE_FROM_FAV:
return {
...state,
favs: state.favs.slice().filter(f => f._id != action.product._id)
}
case SET_DATA:
return {
...state,
data: [...state.data, action.data]
}
case TABBAR:
return {
...state,
tabbar: action.tabbar
}
default:
return state;
}
}
export default products;
reducers/index.js
import prodReducer from './products';
export default combineReducers({
prodReducer
})
Action/product.js
export const SET_PRODUCTS = 'SET_PRODUCTS';
export const ADD_TO_FAV = 'ADD_TO_FAV';
export const REMOVE_FROM_FAV = 'REMOVE_FROM_FAV';
export const SET_DATA = 'SET_DATA';
export const TABBAR = 'TABBAR';
export const setProducts = (products) => {
return {
type: SET_PRODUCTS,
products
};
}
export const addToFav = (product) => {
return {
type: ADD_TO_FAV,
product
};
}
export const removeFromFav = (product) => {
return {
type: REMOVE_FROM_FAV,
product
};
}
export const tabbar = (tabbar) => {
return {
type: TABBAR,
tabbar
};
}
export const setData = (data) => {
return {
type: SET_DATA,
data
};
}
app.js
import React, { useEffect } from 'react';
import Navigator from './navigation/Navigator'
import { Provider } from 'react-redux';
import { store, persistor } from './redux/store';
import { PersistGate } from 'redux-persist/integration/react'
import { firebase } from '#react-native-firebase/messaging'
import AsyncStorage from '#react-native-community/async-storage';
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<StatusBar backgroundColor={'#AD1457'} />
<Navigator />
</PersistGate>
</Provider>
)
}
Your persist config seems alright to me. Can you add the whitelist key too?
const persistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
whitelist:['products','favs']
}
On closer inspection, the persist config is not entirely correct. In order for blacklist and whitelist to work, they have to match keys of a reducer to which you apply the persist config - in this case, rootReducer, which only has one key - prodReducer.
What you want is configure persistence of your products reducer specifically in addition to root. The docs call this nested persists. You can do this in your reducers/index.js:
import AsyncStorage from '#react-native-community/async-storage';
import { persistStore, persistReducer } from 'redux-persist'
import prodReducer from './products';
const productsPersistConfig = {
key: 'products',
storage: AsyncStorage,
blacklist: ['data', 'tabbar'],
}
export default combineReducers({
prodReducer: persistReducer(productsPersistConfig, prodReducer),
})
You can then remove the blacklist from your main persistConfig.
Have you tried removing the app completely and doing a fresh install after the persist config has been added? Without that, data persisted previously is still there.
I have fixed this issue
Now, i have created two reducers . One for blacklist and one for whitelish.
Then combine it
export default combineReducers({
whitelistReducer, blackListReducer
})
and in store create a persistConfig in which i gave the respected reducer in blacklist and whitelist
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['whitelistReducer'],
blacklist: ['blackListReducer']
}
I get the following error using redux in native react
Invariant Violation: Invariant Violation: Could not find "store" in
the context of "Connect(Lista)". Either wrap the root component in a
, or pass a custom React context provider to and
the corresponding React context consumer to Connect(Lista) in connect
options.
My code is the following
SettingStore
import {createStore, applyMiddleware} from 'redux';
import Reducers from './Reducer'
import thunk from 'redux-thunk'
export default SettingStore = () => {
let store = createStore(Reducers, applyMiddleware(thunk))
return store
}
my index reducer
import {combineReducers} from 'redux';
import loginreducer from './login.reducer'
export default combineReducers({
login: loginreducer,
})
my index action
import {FETCHING_GETDATA_PARVU} from '../Constante'
import {GetParv} from '../Api/Parvularia.api'
export const getParvuSuccess = (data) => {
return {type: FETCHING_GETDATA_PARVU, data}
}
export const GetParvu = () => {
return (dispatch) => {
dispatch(getData())
GetParv(1)
.then(([response, json]) => {
dispatch(getParvuSuccess(json))
})
.catch((error) => console.log(error))
}
}
This is the maintab.reducer of my reducer
import {FETCHING_GETDATA_PARVU} from '../Constante'
const initialState = {
data: [],
isFeching: false,
error: false
}
export default dataReducer = (state = initialState, action) => {
switch(action.type) {
case FETCHING_GETDATA_PARVU:
return {
...state,
data: action.data,
isFeching: false
}
default:
return state
}
}
and let's say this is my app.js of the redux structure
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import {Provider} from 'react-redux';
import Lista from '../screens/Educadora/Lista';
import SettingStore from './SettingStore'
let store = SettingStore()
const Ap = () => (
<Provider store={store}>
<Lista/>
</Provider>
)
export default Ap;
This last part is the one that generates me more doubt, I think that this is my error but I do not know why I am new in react native
Edit
This is where I want to show the query made with redux
import { connect } from 'react-redux'
import {GetParvu} from '../../Redux/Actions'
class Lista extends React.Component {
componentWillMount() {
this.props.GetParvu()
render(){
return(algoaca)
}
}
}
const mapStateToProps = state => {
return {
parvu: state.data
}
}
const mapDispatchToProps = dispatch => {
return {
GetParvu: () => {
return dispatch(GetParvu(1))
}
}
}
AppRegistry.registerComponent('EqualsMobile', () => Lista);
export default connect(mapStateToProps, mapDispatchToProps)(Lista);
You have connecting your component in wrong way. connect() is high order component that take mapStateToProps as an argument to map store to your component.
You can use it in your component like this.
function mapStateToProps(state){
return {state}
}
export default connect(mapStateToProps)(your_component_name);
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)
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.
i'm developing an android application whereby my screen has to depends on whether the user is logged in or not. The login data is stored inside the AsyncStorage.
By the time when the apps start, it should get the data from AsyncStorage and make it as the default state. How can i achieve that ?
Below is my redux structure
index.android.js
import {
AppRegistry,
} from 'react-native';
import Root from './src/scripts/Root.js';
AppRegistry.registerComponent('reduxReactNavigation', () => Root);
Root.js
import React, { Component } from "react";
import { Text, AsyncStorage } from "react-native";
import { Provider, connect } from "react-redux";
import { addNavigationHelpers } from 'react-navigation';
import getStore from "./store";
import { AppNavigator } from './routers';
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
const mapStateToProps = (state) => ({
nav: state.nav
});
const user = {};
class App extends Component {
constructor(){
super();
}
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const AppWithNavigationState = connect(mapStateToProps)(App);
const store = getStore(navReducer);
export default function Root() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
user reducers
import {
REGISTER_USER,
UPDATE_USER,
LOGIN_USER
} from '../../actions/actionTypes';
import { handleActions } from 'redux-actions';
**// here is the default state, i would like to get from asyncstorage**
const defaultState = {
isLoggedIn: false,
user:{},
};
export const user = handleActions({
REGISTER_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
LOGIN_USER: {
next(state, action){
return { ...state, user: action.payload, isLoggedIn: true }
}
},
}, defaultState);
You can use the component lifecycle method componentWillMount to fetch the data from AsyncStorage and when the data arrive you can change the state.