Calling a function from another component with redux - redux

Trying to toggle open a modal from another component with redux. Almost there but not really sure how to finish it up - been looking around for a clear answer!
On the HomeScreen component (the main component), to activate the openModal method on the AddCircleModal component, causing the Modal to open.
The Modal - AddCircleModal: Using redux, I can successfully close the modal if I open it manually in the code
class AddCircleModal extends React.Component {
state = {
top: new Animated.Value(screenHeight),
modalVisible: false
}
// componentDidMount() {
// this.openModal()
// }
openModal = () => {
Animated.spring(this.state.top, {
toValue: 174
}).start()
this.setState({modalVisible: true})
}
closeModal = () => {
Animated.spring(this.state.top, {
toValue: screenHeight
}).start()
this.setState({modalVisible: false})
}
render() {
return (
<Modal
transparent={true}
visible={this.state.modalVisible}
>
<AnimatedContainer style={{ top: this.state.top, }}>
<Header />
<TouchableOpacity
onPress={this.closeModal}
style={{ position: "absolute", top: 120, left: "50%", marginLeft: -22, zIndex: 1 }}
>
<CloseView style={{ elevation: 10 }}>
<FeatherIcon name="plus" size={24} />
</CloseView>
</TouchableOpacity>
<Body />
</AnimatedContainer>
</Modal>
)
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
closeModal: () =>
dispatch({
type: "CLOSE_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddCircleModal)
HomeScreen: The other component that I want to toggle from
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
// this.openModal() - what do I do with this to call the openModal function in the modal component?
console.log('owwwww weeeee')
}
render() {
return (
<SafeAreaView>
<HomeHeader openModal={this.toggleOpenCircleModal}/> - this method is because I'm calling toggleOpenCircleModal from a button in the header of the home screen. It works as it outputs the 'owwwww weeeee' string to the console.
<SafeAreaView style={{ width: '100%', flex: 1}} />
<AddCircleModal />
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action }
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
modalToggle: The reducer
const initialState = {
action: ""
}
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return { ...state, action: "openModal" }
case "CLOSE_MODAL":
return { ...state, action: "closeModal" }
default:
return state
}
}
export default modalToggle

Right now, your components are not using redux store properly.
When you use mapStateToProps, you can access every redux store reducer. You can access every prop in them and these will be sent via props in your connected component. For instance:
//redux
import { connect } from 'react-redux'
import styles from './Styles'
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {
};
}
toggleOpenCircleModal = () => {
if(this.props.action === 'openModal') {
this.props.openModal();
} else {
this.props.closeModal();
}
}
render() {
const { action } = this.props; // this.props.action is coming from Redux Store
return (
<SafeAreaView>
{action} // this will be 'openModal'
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return { action: state.action } // this will be available in HomeScreen as props.action
}
function mapDispatchToProps(dispatch) {
return {
openModal: () =>
dispatch({
type: "OPEN_MODAL"
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
You can read more on https://react-redux.js.org/using-react-redux/connect-mapstate.
The same goes for mapDispatchToProps. In your case, openModal will be available in props.openModal in your HomeScreen component. You can read more on https://react-redux.js.org/using-react-redux/connect-mapdispatch
Based on this, in your AddCircleModal component, you should be using props.action to evaluate if the modal should be visible. (props.action === 'openModal').
If you want to open or close your modal, you'll just need to call the openModal or closeModal dispatch call in your component. In HomeScreen component, in your function toggleOpenCircleModal, you will call openModal() or closeModal() depending on props.action === 'openModal'.
Lastly, you should be using just a boolean value to check for the modal visibility, instead of a string, if that's the only purpose for your reducer.
const initialState = false;
const modalToggle = (state = initialState, action) => {
switch (action.type) {
case "OPEN_MODAL":
return true;
case "CLOSE_MODAL":
return false;
default:
return state
}
}
export default modalToggle

Related

Helper method takes to much time to retrieve data from Firebase causing rendering error

Loading the state fail because the data to be displayed hasn't loaded yet.
At first, I put the server logic inside of my component and it worked, but I will need to re-use this on lots of components, so I decided to put my readUserDate function in a helper method, but now, it doesn't work anymore.
How can I fix this? (thank you in advance)
import config from "./Config"
import firebase from "firebase";
export function readUserData(path) {
firebase.database().ref(path).on('value', function (snapshot) {
console.log(snapshot.val())
return snapshot.val()
});
}
import { readUserData } from "../../../server/Actions"
class Resultats extends Component {
constructor() {
super()
this.state = {
loading: true,
}
}
componentWillMount() {
let path = "devInc"
this.setState({
results: readUserData(path),
loading: false,
})
}
render() {
if (this.state.loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<View style={styles.container}>
<Text>data</Text>
<Text>{this.state.results['-LbstaCmVt1nAWUrTw4T'].date}</Text>
</View>
);
}
}
Try this:
export const readUserData = async() => {
const snapshot = await firebase.database().ref(path).on('value');
return snapshot.val();
};
import { readUserData } from "../../../server/Actions"
class Resultats extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
results: null,
}
}
componentDidMount() {
this.readData();
}
readData = async() => {
let path = "devInc";
this.setState({ loading: true });
const results = await readUserData(path);
this.setState({ loading: false, results });
};
render() {
const { loading, results } = this.state;
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
if (!results) return null;
return (
<View style={styles.container}>
<Text>data</Text>
<Text>{results['-LbstaCmVt1nAWUrTw4T'].date}</Text>
</View>
);
}
}
Suggestion
This could be better done that through redux action

Redux store doesn't update component props

I'm trying to call a API from my store to update the state of a component, here getting the price of a crypto-curency.
I use a clone of my state in return (nextState here) and the log of nextState is well fill with goods price, but my component get only the initialState.
Here the code :
My component
import React from 'react';
import { StyleSheet, Text, View, Button, ImageBackground,TouchableOpacity, Image } from 'react-native';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';
import { connect } from 'react-redux'
class Bitcoin extends React.Component {
constructor(props){
super(props);
this.state = {
}
}
componentDidMount() {
const action = { type: 'PRICES', value: this.state.cryptos}
this.props.dispatch(action)
console.log(this.props.cryptos)
}
componentDidUpdate() {
console.log("Component did Update : ")
console.log(this.props.cryptos)
}
render() {
return (
<View>
<Text style={styles.title}>Bitcoin !</Text>
<Text> {this.props.cryptos[0].price} </Text>
</View>
)
}
}
const styles = StyleSheet.create({
title: {
marginTop: wp("10%")
},
});
const mapStateToProps = (state) => {
return {
cryptos: state.Crypto.cryptos
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatch: (action) => { dispatch(action) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Bitcoin)
My Reducer :
const initialState = { cryptos: [
{
title: "Bitcoin",
id: "BTC",
price: 0
}, {
title: "Ethereum",
id: "ETH",
price: 0
}, {
title: "Ripple",
id: "XRP",
price: 0
}], toast: 0}
function Crypto(state = initialState, action) {
let nextState
switch (action.type) {
case 'PRICES':
nextState = {...state}
fetch('https://min-api.cryptocompare.com/data/pricemulti?fsyms=ETH,BTC,XRP&tsyms=EUR&api_key=c3b60840403013f86c45f2ee97571ffdf60072fafff5c133ed587d91088451b6')
.then((response) => response.json())
.then((responseJson) => {
nextState.cryptos[0].price = responseJson.BTC.EUR.toString()
nextState.cryptos[1].price = responseJson.ETH.EUR.toString()
nextState.cryptos[2].price = responseJson.XRP.EUR.toString()
console.log("NextState :");
console.log(nextState.cryptos);
return nextState
})
.catch((error) => {
console.error(error);
});
return nextState
case 'TOAST':
nextState = {...state}
default:
return state
}
}
export default Crypto
Welcome to StackOverflow.
I guess you are new to Redux workflow. So here it is.
Actions describe an action. The reducer receive the action and specify how the store is changing.
Action must be plain javascript object. And reducer functions must be pure !
Here what is forbidden to do inside reducers :
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
In your example, by calling fetch. You're making an API Call.
I invite you to read this guide to know more about : How to introduce API call and asynchronous into your redux app. (https://redux.js.org/advanced/async-actions)

React native component function promise return url onNavigationStateChange

I have a component, this component is nothing but a WebView.
I make a call to this component and I want a result back, through promises.
I have to make sure that after the WebView is loaded and running onNavigationStateChange, I have to return a Promise to return the result.
Main:
import * as React from 'react';
import { View } from 'react-native';
import ShortUrl from './ShortUrl';
export default class App extends React.Component {
componentDidMount() {
this.shortUrl
.init('https://www.cineblog.life/?trdownload=0&trid=24045&movie=0')
.then(uid => {
console.log('URL: ' + uid);
})
.catch(err => alert('error: ' + err));
}
render() {
return (
<View>
<ShortUrl
ref={r => (this.shortUrl = r)}
style={{ width: 0, height: 0, backgroundColor: '#000' }}
/>
</View>
);
}
}
ShortUrl:
import * as React from 'react';
import { View, WebView } from 'react-native';
export default class ShortUrl extends React.Component {
constructor() {
super();
this.state = {
initialUrl: '',
init: false,
//promise:
};
}
init(initialUrl) {
this.setState({ initialUrl, init: true });
return new Promise(async (resolve, reject) => {
resolve('OK');
});
}
onNavigationStateChange = navState => {
const { initialUrl } = this.state;
if (initialUrl !== navState.url) {
return new Promise(async (resolve, reject) => {
resolve(navState.url);
});
}
};
render() {
const { initialUrl, init } = this.state;
if (!init) return null;
return (
<View>
<WebView
source={{
uri: initialUrl,
}}
onNavigationStateChange={this.onNavigationStateChange}
style={{ flex: 1 }}
/>
</View>
);
}
}
Link expo: Here
I can't fully understand your question, but it seems you're simply looking for a function passed as prop to your child component. Once your promise is completed just call that function you have in prop and pass it a parameter, in order to handle it in the parent component.
In your Main:
[...]
<ShortUrl
ref={r => (this.shortUrl = r)}
style={{ width: 0, height: 0, backgroundColor: '#000' }}
handleResult={ resultArrivingFromChild => { // do what you want with the result }}
/>
In ShortUrl:
onNavigationStateChange = navState => {
[...] // do what you have to do
this.props.handleResult(resultYouWantToSendBack);
};

React Navigation Preventing Going back to loading screen, reset not working

I have a React Native application which I have implemented. Currently the app opens up on a loading screen which after mounting checks the firebase.auth().onAuthStateChanged(...) feature.
The app basically decides whether or not to got to the login screen or to main screen depending on whether or not the user is already authenticated.
It is implemented like this:
Main Navigator:
const MainNavigator = TabNavigator({
auth: {
screen: TabNavigator({
login: { screen: LoginScreen },
signup: { screen: SignupScreen }
}, {
initialRouteName: 'login',
tabBarPosition: 'top',
lazy: true,
animationEnabled: true,
swipeEnabled: true,
tabBarOptions: {
labelStyle: { fontSize: 12 },
showIcon: true,
iconStyle: { width: 30, height: 30 }
}
})
},
main: {
screen: StackNavigator({
notes: { screen: NotesScreen }
}, {
initialRouteName: 'notes'
})
},
loading: { screen: LoadingScreen }
}, {
initialRouteName: 'loading',
lazy: true,
swipeEnabled: false,
animationEnabled: false,
navigationOptions: {
tabBarVisible: false
}
});
Loading Screen:
class LoadingScreen extends Component {
componentDidMount() {
const { navigate } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
navigate('main');
} else {
navigate('auth');
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
const styles = {
spinnerStyle: {
flexDirection: 'row',
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
};
This works well except for one issue.
When I press the hardware back button for Android, it goes to the application loading screen which obvious is undesired. How do I prevent that?
EDIT:
I've tried the following and it didn't work either:
const resetAction = (routeName) => NavigationActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName })],
key: null
});
class LoadingScreen extends Component {
componentDidMount() {
const { dispatch } = this.props.navigation;
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.setUser(user);
dispatch(resetAction('main'));
} else {
dispatch(resetAction('auth'));
}
});
}
render() {
return (
<View style={styles.spinnerStyle}>
<Spinner size="large" />
</View>
);
}
}
use a switch navigator until the user logs in(loading and login page ) successsfully after that use a stack navigator(user homepage and otherpages which follow).
switchNavigator(loading, login, stackNavigator)
stackNavigator(user homepage,....)
Try a custom navigation component with custom back button support. Dont forget to add the reducer to yoru combine reducers function.
Create a navigation component:
import React, { Component } from 'react';
import { BackHandler } from 'react-native';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import MainNavigator from './MainNavigator';
class AppWithNavigationState extends Component {
componentDidMount () {
BackHandler.addEventListener('hardwareBackPress', () => {
this.props.dispatch({
type: 'Navigation/BACK'
});
return true;
});
}
componentWillUnmount () {
BackHandler.removeEventListener('hardwareBackPress');
}
render () {
return (
<MainNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}/>
);
}
}
export default connect((state) => ({ nav: state.nav }))(AppWithNavigationState);
Create a navigation reducer:
import { NavigationActions } from 'react-navigation';
import MainNavigator from './MainNavigator';
import { NAVIGATION_ON_SIGN_IN } from '../redux/actions/ActionTypes';
import { BackHandler } from 'react-native';
const initialState = MainNavigator.router.getStateForAction(MainNavigator.router.getActionForPathAndParams('loading'));
function appShouldClose (nextState) {
const { index, routes } = nextState;
return index === 0 || routes[1].routeName === 'auth';
}
export default (state = initialState, action) => {
const { router } = MainNavigator;
let nextState;
switch (action.type) {
case NavigationActions.BACK:
nextState = router.getStateForAction(action, state);
appShouldClose(nextState) && BackHandler.exitApp();
break;
default:
nextState = router.getStateForAction(action, state);
}
return nextState || state;
};
it is my solution :)
I have StageArea page. it is bridge between from login to timeline . User is not login then go to LoginPage. User is login then go to Timeline. User press back button then again go to TimeLine page not go to login page .( Sory for my english)
import React, { Component } from 'react';
import { View } from 'react-native';
import LoginForm from './LoginForm';
import Timeline from './Timeline';
import firebase from 'firebase';
import InitialPage from './InitialPage'
class StageArea extends Component {
state = {isLoggin:''};
componentWillMount(){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.setState({ isLoggin:true})
}else {
this.setState({ isLoggin:false})
}
})
}
render() {
if(this.state.isLoggin)
{
return(<Timeline/>);
}
else if (this.state.isLoggin===false) {
return(<LoginForm/>);
}
}
}
export default StageArea;
Write the code below ,
static navigationOptions = {
header:null
};
Just before
render() {
return (
on the NotesScreen,There will not be any back button.

Async React-select with redux

I am trying to make an async react-select component with redux but somehow not able to get search results in the dropdown. Very new to this. Please help :)
import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import Select from 'react-select';
import { fetchInstitutionsIfNeeded } from '../../actions/institutions';
class Signup extends React.Component {
constructor(props) {
super(props);
this.state = {
value: null
};
this.getInstitutions = this.getInstitutions.bind(this);
this.onChange = this.onChange.bind(this);
}
onChange(input) {
this.setState({
value: input
});
}
getInstitutions(input) {
const { dispatch } = this.props;
if (!input) {
return Promise.resolve({ options: [] });
}
dispatch(fetchInstitutionsIfNeeded(input));
}
render() {
let options = this.props.options;
return (
<div>
<Select.Async
name="institute"
value={this.state.value}
autoload={false}
onChange={this.onChange}
loadOptions={this.getInstitutions}
/>
</div>
);
}
}
const mapStateToProps = (state) => ({
options: state.institutions.options
});
export default connect(mapStateToProps)(Signup);
Also options object is also properly formatted and is getting updated properly in redux store but not reflecting back in select async's dropdown.
Try this, we can also return from action but it breaks the whole idea of reducers.
// actions.js
export const getProducts = (input = '') => async (dispatch) => {
dispatch({
type: GET_PRODUCTS_PENDING,
payload: {},
});
try {
const response = await axios.get(`/api/v1/products/`);
dispatch({
type: GET_PRODUCTS_SUCCESS,
payload: response.data,
});
} catch (error) {
// handle errors
}
};
// components.jsx
class Signup extends PureComponent {
async getProductsForSelect(input) {
await this.props.getProducts(input);
return this.props.product.options;
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<AsyncSelect
isMulti
cacheOptions
defaultOptions
loadOptions={(e) => this.getProductsForSelect(e)}
/>
</form>
);
}
}

Resources