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 :)
Related
I am trying to snapshot test with Jest, Expo, React Navigation and my whole app uses hooks only. I'd like to eventually make these into e2e tests where Jest clicks through and snapshot tests everything but I can't even get react navigation to render. My snapshot after the expo loader always shows "null." I followed the basic example from the tabs starter that comes with expo init but the way it outlines how to setup the mocks simply doesn't work for my app. I've tried all sorts of things but nothing works.
App.tsx
import { Ionicons } from '#expo/vector-icons';
import { AppLoading } from 'expo';
import { Asset } from 'expo-asset';
import * as Font from 'expo-font';
import React, { useState } from 'react';
import { YellowBox } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import { useScreens } from 'react-native-screens';
import { Provider as RxProvider } from 'reactive-react-redux';
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import SnackBar from './components/UI/Snackbar';
import { socketMiddleware } from './lib/socketMiddleware';
import SwitchNavigator from './navigation/AppNavigator';
import rootReducer from './reducers/rootReducer';
import { theme } from './Theme';
const middleware = [thunk, socketMiddleware()];
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export const store = createStore<iAppState, any, any, any>(
rootReducer,
composeEnhancers(applyMiddleware(...middleware))
);
// Must be called prior to navigation stack rendering
useScreens();
YellowBox.ignoreWarnings(['Require cycle:']);
const App = (props) => {
const [isLoadingComplete, setLoadingComplete] = useState(false);
if (!isLoadingComplete && !props.skipLoadingScreen) {
return (
<AppLoading
startAsync={loadResourcesAsync}
onError={handleLoadingError}
onFinish={() => handleFinishLoading(setLoadingComplete)}
/>
);
} else {
return (
<RxProvider store={store}>
<PaperProvider theme={theme}>
<SwitchNavigator />
<SnackBar />
</PaperProvider>
</RxProvider>
);
}
};
const loadResourcesAsync = async () => {
await Promise.all([
Asset.loadAsync([
//nothing
]),
Font.loadAsync({
...Ionicons.font,
TitilliumText250: require('./assets/fonts/TitilliumText22L-250wt.otf'),
TitilliumText800: require('./assets/fonts/TitilliumText22L-800wt.otf')
})
]);
};
const handleLoadingError = (error: Error) => {
console.warn(error);
};
const handleFinishLoading = (setLoadingComplete) => {
setLoadingComplete(true);
};
export default App;
App.test.tsx:
import React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import NavigationTestUtils from 'react-navigation/NavigationTestUtils';
import renderer from 'react-test-renderer';
import App from './App';
import { theme } from './Theme';
jest.mock('expo', () => ({
AppLoading: 'AppLoading'
}));
jest.mock('react-native-screens');
jest.mock('react-native-paper');
jest.mock('redux');
jest.mock('reactive-react-redux');
jest.mock('./navigation/AppNavigator', () => 'SwitchNavigator');
describe('App', () => {
jest.useFakeTimers();
beforeEach(() => {
NavigationTestUtils.resetInternalState();
});
// success
it(`renders the loading screen`, () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
// this snapshot is always null
it(`renders the root without loading screen`, () => {
const tree = renderer
.create(
<PaperProvider theme={theme}>
<App skipLoadingScreen></App>
</PaperProvider>
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
/navigation/AppNavigator.tsx:
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import LoginStack from './LoginStack';
import TabStack from './TabStack';
/** The most root navigator which allocates to the others. */
const SwitchNavigator = createAppContainer(
createSwitchNavigator(
{
LoginStack: LoginStack,
TabStack: TabStack
},
{
initialRouteName: 'LoginStack'
}
)
);
export default SwitchNavigator;
I was having a similar issue and found a fix via: https://github.com/expo/expo/issues/7155#issuecomment-592681861
Seems like the act() worked magically for me to stop it from returning null (not sure how)
Update your test to use it like this:
import { act, create } from 'react-test-renderer';
it('renders the root without loading screen', () => {
let tree;
act(() => {
tree = create(
<PaperProvider theme={theme}>
<App skipLoadingScreen></App>
</PaperProvider>
);
});
expect(tree.toJSON()).toMatchSnapshot();
});
Note: When I changed how I was using the imports from 'react-test-renderer' my tests couldn't find act() as a function, a simple re-install of npm packages solved this problem!
I am trying to figure put how to use Redux-Persist. I have the following code in my index.js:
import { AppRegistry, View } from "react-native";
import React from "react";
import App from "./App";
import { name as appName } from "./app.json";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import storeConfig from "./app/config/store";
const AppRedux = () => (
<Provider store={storeConfig.store}>
<PersistGate presistor={storeConfig.persistor} loading={null}>
<App />
</PersistGate>
</Provider>
);
AppRegistry.registerComponent(appName, () => AppRedux);
When I run it I get the following message:
Unhandled JS Exception: TypeError: Cannot read property 'subscribe' of undefined
BUT, When i comment out PersistGate I have no problems and my store gets rehydrated.
The reason I am trying to keep PersistGate is to have splash screen while rehydrating. Any suggestions on how to solve this problem?
P.S. This is what I have in store file:
export default () => {
const pReducer = persistReducer(persistConfig, reducers);
const store = createStore(pReducer, {}, applyMiddleware(logger));
const persistor = persistStore(store);
return { store, persistor };
};
I'm trying to use redux in my project I make my code like tutorial but it won't work and give error
I see most of the question about this error but I already done what they say the soultion like I should warp my root componet with provider or use connect
Invariant Violation: Could not find "store" in the context of "Connect(App)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(App) in connect options.
index.js
import React from 'react';
import {AppRegistry} from 'react-native';
import {Provider} from 'react-redux';
import App from './App';
import { name as appName } from './app.json';
import placesReducer from './src/store/reducers/places';
import{createStore} from 'redux'
import configureStore from './src/store/configureStore';
const store = configureStore();
const RNRedux = () => (
<Provider store={store}>
<App/>
</Provider>
);
AppRegistry.registerComponent(appName, () => RNRedux);
APP.js
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { connect } from "react-redux";
class App extends Component {
render() {
return (
<View style={styles.container}>
some code
</View>
);
}
}
const mapStateToProps = state => {
return {
some code
};
};
const mapDispatchToProps = dispatch => {
return {
some code
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
my store
import {combineReducers, createStore} from 'redux';
import placesReducer from './reducers/places';
const rootReducer = combineReducers({
places: placesReducer
});
const configureStore = () => createStore(rootReducer)
export default configureStore;
const configureStore = () => createStore(rootReducer)
configureStore in this case is a variable with the return value of createStore, you most likely want it to be a function.
do you have to call configureStore as callback?
can you call it directly like that:
const configureStore = createStore(rootReducer);
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 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.