How to map state to props on the initial file (App.js or Index.js)? - react-native

This is probably something very basic. There is a spinner on my App where the routes and providers are declared. This must be reading the redux store, in particular spinner.visible and map to state so I can hide/show the <Spinner> element.
But as I said...this is the entry file of the app. I know how to map it to props using connect, but looks like I can't use connect/mapStateToProps on my entry file.
This works very good, but I don't think that using a subscribe is the best way. I'd like to make the spinner be capable to read the store directly in an elegant way. Any suggestions ?
import React from 'react'
import {Provider} from 'react-redux'
import {View} from 'react-native'
import {createStore, applyMiddleware} from 'redux'
import Spinner from 'react-native-loading-spinner-overlay'
import ReduxThunk from 'redux-thunk'
import reducers from './reducers'
import Routes from './config/routes'
import {getReady} from './services/registration'
import {setAppInitialLoad, setAppSpinner} from './actions/AppActions'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
initialized: false,
spinner: {
visible: false,
text: ''
}
}
store.subscribe(() => {
//ToDo: I really hope to find an elegant solition for this.
//Since it' the top level JS file of the app, I can't use
//connect/mapStateToProps to map the props :(
const spinner = store.getState().AppReducer.spinner
if(spinner.visible != this.state.spinner.visible) {
this.setState({
spinner: {
visible: spinner.visible,
text: spinner.text
}
});
}
}
)
}
componentDidMount() {
store.dispatch(setAppSpinner({ visible: true, text: 'Loading...'}))
getReady().then(response => {
store.dispatch(setAppInitialLoad(response.data.data))
store.dispatch(setAppSpinner({ visible: false, text: ''}))
this.setState({initialized: true})
})
}
render() {
if (this.state.initialized) {
return (
<View>
<Provider store={store}>
<Routes/>
</Provider>
<Spinner visible={this.state.spinner.visible} textContent={this.state.spinner.text}
textStyle={{color: '#000'}}/>
</View>
)
} else {
return (
<View style={{backgroundColor: 'yellow', flex: 1}}/>
)
}
}
}
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk))
export default App;

Use can use store variable
(In your code, it here: const store = createStore(reducers, {}, ...)
store variable has some method, you can read at here (https://redux.js.org/basics/store)

Related

null is not an object (evaluating 'this.nativeCommandsModule.push')

I am using WIX-React-Native-Navigation and while pushing a component in the stack I am getting this error.
Error
One thing to say, I am using Viro-React ARScene Navigation (Scene Navigation) in WIX-React-Native-Navigation.
Here's my code
index.js
import { AppRegistry } from 'react-native';
var OpenDoor = require('./app/base')
import Stack from './app/navigation';
import { Navigation } from "react-native-navigation";
AppRegistry.registerComponent('ViroSample', () => OpenDoor);
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: Stack
}
});
});
navigation.js
import { Navigation } from "react-native-navigation";
import Base from './base';
import Menu from './components/Menu/menu';
Navigation.registerComponent('Base', () => Base);
Navigation.registerComponent('Menu', () => Menu);
const Stack = {
children: [{
component: {
name: 'Base'
},
}
]
};
export default Stack;
base.js
import React, {Component} from 'react';
import {
Alert,
View
} from 'react-native';
import {ViroARSceneNavigator} from 'react-viro';
import { Navigation } from 'react-native-navigation';
import APIKeys from './index';
import Camera from './components/Camera/camera';
import MenuButton from './components/Menu/menu_button';
class Base extends Component {
constructor(props) {
super(props);
this.navigateScreen = this.navigateScreen.bind(this);
}
navigateScreen(location) {
Navigation.push(this.props.componentId, {
component: {
name: location
}
});
}
render() {
return(
<View style={styles.container}>
<ViroARSceneNavigator
initialScene = {{
scene: Camera
}}
apiKey = {APIKeys.ViroReact}
style = {styles.ar_scene}
/>
<MenuButton navigation={this.navigateScreen}/>
</View>
);
}
};
const styles = {
container: {
flex: 1
}
};
export default Base;
module.exports = Base;
Correct me, If I am wrong somewhere.
What I am doing is, creating an application where users can decorate home virtually with the help of Augmented Reality. The mobile app is created on React-Native while the library used for augmented reality is Viro-React.
So, When I want to navigate from One Screen to another, then while pushing component in the stack I am getting this error.
Thanks.

Is there a way to read the options before use the mergeOptions function in react native navigation v2?

Is there a way to read the options before using the mergeOptions function.
I'm trying to add a sideMenu that opens and closes with the same button. But to handle that logic, Instead of making use of redux, I want to read the options before the merge, so I can simply do something like visible: !pastVisible.
navigationButtonPressed({ buttonId }) {
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: false
}
}
});
console.log(`Se presiono ${buttonId}`);
}
So basically I want to read the value of the visible option before changed it.
By now, I can only achieve this using redux.
import React, {Component} from 'react';
import {View, Text} from 'react-native';
import { Navigation } from 'react-native-navigation';
import { connect } from 'react-redux';
import { toggleSideMenu } from './../../store/actions/index';
class SideDrawer extends Component {
constructor(props) {
super(props);
Navigation.events().registerComponentDidDisappearListener(({ componentId }) => {
this.props.toggleSideMenu(false);
});
}
render() {
return (
<View>
<Text>This is the sidedrawer</Text>
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSideMenu: (visible) => dispatch(toggleSideMenu(visible))
};
};
export default connect(null, mapDispatchToProps)(SideDrawer);
Then I just add the listeners to the sidemenu component. Depending on the case, I update the current state of the component (visible or not).
Finally on the components where I want to use the side drawer button I just implement the navigationButtenPressed method. Then I just call the reducer to know the current visible state and toggled it.
navigationButtonPressed({ buttonId }) {
const visible = !this.props.sideMenu;
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: visible
}
}
});
this.props.toggleSideMenu(visible);
}
If there is a more easy way to achieve this I'll be glad to know about it.

Redux error in react-native

I'm trying to implement redux to show balance in multiple screens as I update balance in single screen it should reflect in all other screens/components.
I'm pretty new to redux. As you know with complexity around redux, its even making difficult to implement it.
I followed some examples in GitHub and youtube and started implementing it .
Under Actions folder I have. following two files
counteractions.js
import * as types from './actionTypes.js';
//ActionCreator methods
export function updateBalance(balanceInfo) {
return {
type: types.LEDGER_BALANCE,
payLoad: { balanceInfo }
}
}
Under Reducers folder.I have this file
balance.js
import * as types from '../actions/actionTypes.js';
const initialState = {
balance: 0
}
// reducer functions .. accepts current/initial state , actions and returns new state
const balanceReducer=(state,action)=>
{
switch (action.type) {
case types.LEDGER_BALANCE:
return {
balance: action.payload.balanceInfo
}
break;
default:
break;
}
}
export default balanceReducer;
in ConfigureStore.js
import {createStore} from 'redux';
import rootReducer from './reducers/index.js';
import balanceReducer from './reducers/balance.js';
const initailState = {
balance: 0,
}
export const store=createStore(balanceReducer,balanceReducer);
App.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import { Provider } from 'react-redux';
//Provider - makes redux store available to connect() class in component hierarchy below
import { applyMiddleware, createStore, compose, combineReducers } from "redux";
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import rootReducer from './reducers/index.js';
//import store from './configureStore.js';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
TextInput
} from 'react-native';
import ReduxDemo from "./reduxDemo.js";
import { store, reducer } from './balanceDemo.js';
const instructions = Platform.select({
ios: 'Press Cmd+R to reload,\n' +
'Cmd+D or shake for dev menu',
android: 'Double tap R on your keyboard to reload,\n' +
'Shake or press menu button for dev menu',
});
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
balancelocal: '',
}
}
_updateLedger = () => {
// store.dispatch({ type: 'BALANCE', payLoad: '500' });
store.dispatch({ type: 'BALANCE', payLoad: 'Your balance is 8000 MUR' });
}
render() {
store.subscribe(() => {
this.setState({
balancelocal: store.getState(),
})
//this.balanceInfo = store.getState().balance;
// alert(this.state.balancelocal);
});
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._updateLedger}>
<Text>Update balance</Text>
</TouchableOpacity>
<TextInput style={{height:100,width:400}} value={this.state.balancelocal}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
I'm yet to complete configure store file. and. I'm wondering. where I have to subscribe and dispatch actions ..
I want to update balance with button click from app.js
I have. to update balance in another page automatically..
Please guide me to understand and implement redux .Please suggest better folder structure and better way to implement redux.
There is quite a bit here to understand.
The basic workflow is (you can have the receiving component as a different one)
Component Button > Action > Reducer > Component Props > Render
To achieve this you need both the setup of the store and the invoking of the "event" through redux.
Below is an example (excuse if not perfect, just typed into here), but the way the other component gets the value from the action is becuase it uses the 'connect' HOC. Everytime redux gets a state change it calls all components that are 'connected'. Here we take the updated balance and return it as part of the 'mapStateToProps' function, which is just setting the components props with that object. The balance is then accessed via this.props.balance and displayed.
This becomes more useful if you want to either call an api in the action and store the result in the reducer. All connected components will then get that new data.
Note1: I have only used redux-thunk middleware to dispatch so forgive me for using that.
Note2: This is a simple example. When the app gets better you will need to prevent over-rendering since any reducer changes will invoke all connected components. I use reselect here.
Setup
reducers.js
import { combineReducers } from 'redux';
import { balanceReducer } from 'balanceReducer';
export default combineReducers({
balanceReducer
})
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import combineReducers from './reducers'
export default function configureStore() {
let store = createStore(combineReducers, applyMiddleware(thunk));
return store;
}
index.js
import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';
import { Provider } from 'react-redux';
import configureStore from './store';
import Component1 from './component1';
const store = configureStore()
const myapp = () => (
<Provider store={store}>
<View>
<Component1 />
<View>
</Provider>
)
AppRegistry.registerComponent('myapp', () => myapp);
Components
component1.js (key part is the connect HOC)
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { updateBalance } from './action';
class Component1 extends Component {
_updateLedger = () => {
this.props.updateBalance(500);
}
render() {
const { balance } = this.props;
return (
<View>
<TouchableOpacity onPress={this._updateLedger}>
<Text>Update balance</Text>
</TouchableOpacity>
<Text>{balance}</Text>
</View>
)
}
}
function mapStateToProps(state) {
return {
balance: state.balanceReducer.balance
}
}
function mapDispatchToProps(dispatch) {
return {
updateBalance = (balanceInfo) => dispatch(updateBalance(balanceInfo))
};
}
export default connect(
mapStatetoProps,
mapDispatchToProps
)(Component1)
action.js
export function updateBalance(balanceInfo) {
return {
type: types.LEDGER_BALANCE,
payLoad: { balanceInfo }
}
}
balanceReducer.js (key part here is to return new state)
const initialState = {
balance: 0,
}
export function balanceReducer(state = initialState, action) {
if(action.type === types.LEDGER_BALANCE) {
return {
balance: action.payLoad.balanceInfo
}
}
}

Why isn't my component's state computed property updating after Redux store updates its value?

Why isn't my component's state computed property updating after Redux store updates its value?
I am using some helper methods to grab the sub-store via AppStore.getState().ApiStore for my isAuthenticated state property. It seems like when this store value updates, the component state value does not update. Does React Native not watch for store updates in computed component state properties?
My component looks like the below:
// Vendor
import React, { Component } from 'react'
import { AppRegistry, Text, View, StyleSheet, TextInput, Button} from 'react-native'
import AppStore from './Stores/AppStore'
import StoreHelpers from './Stores/StoreHelpers'
// Custom
import Login from './Components/Login/Login'
import Api from './Services/Api.js'
// Styles
const styles = StyleSheet.create({
mainView: {
flex: 1,
padding: 20,
marginTop: 30,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#3b5998',
},
});
// Main App Component
export default class Main extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
isAuthenticated: !StoreHelpers.getApiStore().userBalanceResponse.error // Computed property from store
}
// Enable this for debugging
console.log(this.state)
AppStore.subscribe(() => {
console.log(AppStore.getState())
})
}
render() {
return (
<View style={styles.mainView}>
<Login />
</View>
)
}
}
// skip this line if using Create React Native App
// AppRegistry.registerComponent('AwesomeProject', () => Main);
You can't see it because your component is not subscribed to the store. Anything to do with store is the job of redux and NOT React Native. So if you wrap your component inside react-redux connect and pass in mapStateToProps to it you should get the right computed value.
// ... rest of imports
import { connect } from 'react-redux';
// Main App Component
class Main extends Component {
constructor(props) {
super(props)
this.state = {
isLoading: false,
isAuthenticated: this.props.isAuthenticated,
}
// ... rest of code
}
// ... rest of code
}
const mapStateToProps = (store) => ({
isAuthenticated: !userBalanceResponse: store.userBalanceResponse.error,
});
export default connect(mapStateToProps, null)(Main);
To make it work, make sure that you set up redux store properly. Wrap your root component within a Provider component and pass in store into it. Suppose your root component is called App, then it would look something like the following:
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import Main from 'path-to-main/Main';
// we will pass this store to the Provider
const store = createStore(
reducer,
// ... middlewares etc this is optional
);
export default class App extends Component {
render() {
return(
<Provider store={store}>
<Main />
</Provider>
)
}
}

Getting "Actions may not have an undefined "type""

I'm getting the error:
Actions may not have an undefined "type" property.
But I'm sure I defined it and spelled it right.
App:
import React, {Component} from 'react';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { AsyncStorage } from 'react-native';
import thunk from 'redux-thunk';
import {persistStore, autoRehydrate} from 'redux-persist';
import FBLoginView from '../components/FBLoginView'
import * as reducers from '../reducers';
import Routing from './Routing';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer, undefined, autoRehydrate());
persistStore(store, {
storage: AsyncStorage,
}, () => {
})
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Routing />
</Provider>
);
}
}
Actions:
import * as types from './actionTypes';
export function getFacebookUser(user) {
return {
type: types.GET_FACEBOOK_USER,
user: user,
};
}
Types:
export const GET_FACEBOOK_USER = 'GET_FACEBOOK_USER';
Reducer:
import * as types from '../actions/actionTypes';
const initialState = {
user: {},
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case types.GET_FACEBOOK_USER:
return {
...state,
user: action.user
};
default:
return state;
}
}
Edit (My home.js page)
import React, { Component } from 'react'
import { View, Text, StyleSheet, Image, TouchableHighlight } from 'react-native'
import { Actions } from 'react-native-router-flux'
import {FBLogin, FBLoginManager} from 'react-native-facebook-login'
import FBLoginView from '../components/FBLoginView'
import * as facebookActions from '../actions/facebookActions';
import { connect } from 'react-redux'
import {bindActionCreators} from 'redux'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
login: false
};
console.log(this.props)
}
render() {
let { facebook, actions } = this.props
_onLogin = (e) => {
actions.getFacebookUser(e.profile)
console.log(facebook)
}
_onLogout = (e) => {
console.log(e)
}
return (
<View style={styles.background}>
<Text>{this.state.login ? "Logged in" : "Logged out"}</Text>
<FBLogin
buttonView={<FBLoginView />}
ref={(fbLogin) => { this.fbLogin = fbLogin }}
loginBehavior={FBLoginManager.LoginBehaviors.Native}
permissions={["email","user_friends"]}
onLogin={function(e){_onLogin(e)}}
onLoginFound={function (e){console.log(e)}}
onLoginNotFound={function(e){console.log(e)}}
onLogout={function(e){_onLogin(e)}}
onCancel={function(e){console.log(e)}}
onError={function(e){console.log(e)}}
onPermissionsMissing={function(e){console.log(e)}}
style={styles.fbButton}
passProps={true}
/>
</View>
)
}
}
export default connect(store => ({
facebook: store.facebook.user,
}),
(dispatch) => ({
actions: bindActionCreators(facebookActions, dispatch)
})
)(Home);
const styles = StyleSheet.create({
background: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#00796B',
},
});
I don't think that you're dispatching the action correctly:
actions.getFacebookUser(e.profile)
is an action creator and will just return the action, not dispatch it.
I can't see your Home component that you're hooking up with Connect but I'd guess this is the source of events that you will want to dispatch as actions. Why not try dispatching directly against the store, and then move to use connect to hook up with mapDispatchToProps? Finally you can use bindActionCreators if this is necessary.
There are two very good (free) egghead.io courses that will help here, both by Dan Abramov:
https://egghead.io/courses/getting-started-with-redux
https://egghead.io/courses/building-react-applications-with-idiomatic-redux
and the docs are also very good, but I guess you've seen them.
After seeing more of the code, I can't see how the component you're connecting (Home) is linking its events (for example onLogin) to a dispatch property. I can see it caling its own internal function called _onLogin, but this just in turn call the action creator, it won't dispatch.
The connect function allows you connect properties on a component (here, Home) with the redux store; it effectively links, in your example, the 'onLogin' property of your Home component with a particular action and can then dispatch that action to the store.
So,your Home component needs to accept a property like 'onLogin' that it can then call; mapDispatchToProps is a function you write to marry up your child component's properties to dispatch actions. bindActionCreators is just a further helper to bind to action creators; it may be overkill in your current use case.
Dan Abramov explains this so much better than I can, so see the docs, but also see his answer here:
How to get simple dispatch from this.props using connect w/ Redux?