Redux, Redux-persist, cannot read property "age" of undefined - react-native

I am using Redux with a React Native app, and am getting a red box error when trying to integrate Redux-Persist. As soon as I navigate to the initial screen which has a ProfileForm and has the this.props.age piece of state, I see this:
TypeError: Cannot read property "age" of undefined
I am not quite sure where I am going wrong. I have set initial value of age = 0 in the reducer. So how is it undefined? Here is code that would be relevant:
reducers/UserReducer.js
import { CALCULATE_BMI, CALCULATE_AGE } from '../actions/types';
const INITIAL_STATE = { bmi: 0, age: 0 };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case CALCULATE_BMI:
return { ...state, bmi: action.bmi };
case CALCULATE_AGE:
return { ...state, age: action.age };
default:
return state;
}
};
reducers/index.js
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
import userReducer from './UserReducer';
export default combineReducers({
user: userReducer,
form: formReducer,
});
store/index.js
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import storage from 'redux-persist/es/storage'; // default: localStorage if web, AsyncStorage if react-native
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducers from '../reducers';
const config = {
key: 'root',
storage,
};
const reducer = persistCombineReducers(config, { reducers });
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(reducer, {}, composeEnhancers(applyMiddleware(thunk, logger)));
const persistor = persistStore(store);
return { persistor, store };
};
ProfileForm.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, TouchableOpacity, Alert } from 'react-
native';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm, Field } from 'redux-form';
import * as actions from '../actions';
import DatePickerModal from '../components/DatePickerModal';
class ProfileForm extends Component {
// other code here
<Field name="birthdate" component={DatePickerModal} />
{this.props.age === 0 ? null : (
<Text style={styles.labelStyle}>{`Age: ${this.props.age}`}</Text>
)}
//other code here
}
const mapStateToProps = state => ({
age: state.user.age,
});
export default compose(connect(mapStateToProps, actions), reduxForm({ form: 'Profile', validate }))(
ProfileForm,
);
App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/es/integration/react';
import MainNavigator from './routes';
import configureStore from './store';
const { persistor, store } = configureStore();
export default class App extends Component {
// other code here
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
<MainNavigator />
</PersistGate>
</Provider>
);
}
}
I have tried removing the empty object {} passed to createStore, setting it as undefined, and setting the initial values here instead, but nothing seems to work. Any guidance appreciated.

Related

Trying to navigate from a component that doesn't have the navigation props

I was working on my Authentication and when I was trying to work on the Auto Logout I had some Problems , I coded the functions needed to logout after the expiry time but when I wanted to Navigate back to the login screen after the logout i couldn't because of the Component i was working on ( doesn't have the navigation prop) , I used the docs but still didn't work !
this is my code
import { useSelector } from "react-redux";
import RootNavigation from "./ShopNavigation";
import { NavigationActions } from "#react-navigation/native";
import { createNavigationContainerRef } from "#react-navigation/native";
export const navigationRef = createNavigationContainerRef();
const NavigationContainer = (props) => {
const isAuth = useSelector((state) => !!state.auth.token);
function navigate(name) {
if (navigationRef.isReady()) {
navigationRef.navigate(name);
}
}
useEffect(() => {
if (!isAuth) {
navigate("Login");
}
}, [isAuth]);
return <RootNavigation />;
};
export default NavigationContainer;
And this is the App.js
import { StatusBar } from "expo-status-bar";
import { StyleSheet } from "react-native";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import AppLoading from "expo-app-loading";
import * as Font from "expo-font";
import productsReducer from "./store/reducers/products";
import { composeWithDevTools } from "redux-devtools-extension";
import RootNavigation from "./navigation/ShopNavigation";
import cartReducer from "./store/reducers/cart";
import { useState } from "react";
import ordersReducer from "./store/reducers/orders";
import AuthReducer from "./store/reducers/Auth";
import ReduxThunk from "redux-thunk";
import NavigationContainer from "./navigation/NavigationContainer";
const rootReducer = combineReducers({
products: productsReducer,
cart: cartReducer,
orders: ordersReducer,
auth: AuthReducer,
});
const store = createStore(
rootReducer,
applyMiddleware(ReduxThunk),
composeWithDevTools()
);
const fetchFonts = () => {
return Font.loadAsync({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
};
export default function App() {
const [fontLoaded, setfontLoaded] = useState(false);
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => setfontLoaded(true)}
onError={console.warn}
/>
);
}
return (
<Provider store={store}>
<NavigationContainer />
</Provider>
);
}
You can use the useNavigation hook for this purpose.
import { useNavigation } from '#react-navigation/native';
function View() {
const navigation = useNavigation();
return (
...
);
}
We can use it as usual, e.g.
navigation.navigate("...")

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.

How to fix this error "combineReducers" are read-only. in react-native/react-redux

I'm creating a new todo app in react-native using redux.
On setting up the rootReducer using combineReducers i'm getting the following error.
"combineReducers" is read-only.
// reducers/todos.js
let nextId = 0;
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state, {
id: nextId++,
text: action.text,
completed: false,
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id) ? { ...todos, completed: !todo.completed } : todo)
default:
return state
}
}
export default todos;
created a visibility filter to toggle the todo state.
// reducers/visibilityFilter.js
const visibilityFilter = (state='SHOW_ALL', action) =>{
return state;
}
export default visibilityFilter;
combining the both reducers into index file.
// reducers/index.js
import { combineReducers } from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';
export default combineReducers = combineReducers({
todos,
visibilityFilter
})
Here is my store file
import { createStore } from 'redux';
import rootReducer from '../reducers';
export default store = createStore(rootReducer);
As the message clearly states, you are not allowed to reassign combineReducers. Just change your code to the following, as described in the docs:
export default rootReducer = combineReducers({
todos,
visibilityFilter
})
Why do you have this like this?
export default combineReducers = combineReducers({
todos,
visibilityFilter
})
combineReducers() are written in this manner:
import { combineReducers } from "redux";
export default combineReducers({
todos: () => {
return {};
}
});
Give that a try and remember a reducer must always return an object, a string or a number, this is the minimum requirement for getting started with reducers in order to ensure your app doesn't break and that you can get some content to the screen.
Also, you don't necessarily need to identify your combineReducers() as rootReducer, you can just put it together like so:
import { createStore, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducers from "../reducers";
const store = createStore(reducers, {}, compose(applyMiddleware(thunk)));
export default store;
Lastly, when you have created your store, you need to import it back to your App.js and wire it up with the Provider tag like so for example:
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import {
createBottomTabNavigator,
createStackNavigator,
createAppContainer
} from "react-navigation";
import { Provider } from "react-redux";
import store from "./store";
And:
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<View style={styles.container}>
<MainNavigator />
</View>
</Provider>
);
}
}

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

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);
}

react-native / redux - action not working?

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>
);
}
}