React-Native, Redux, and Thunk: Async Function Executing Twice But Not Populating State - react-native

After my previous attempt failed, I ran git stash and started over, and the code inside return (dispatch) finally executed. Unfortunately, it began to execute twice, but still not populate the state (or by executing twice it overwrote the state).
Here is my code:
DivisionManagement\index.js
import React, {Component} from "react";
import {View, FlatList, Alert} from "react-native";
import {withNavigation} from "react-navigation";
import {connect} from "react-redux";
import PortableButton from "./../../components/PortableButton";
import masterStyles, {listPage, bigButtonStyles} from "./../../styles/master";
import DivisionManagementRow from "./DivisionManagementRow";
import { loadDivisions } from "../../redux/actions/divisionActions";
class DivisionManagmementScreen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log(`Entered componentDidMount(), props: ${JSON.stringify(this.props)}`);
this.props.getAllDivisions(1);
console.log(`props again: ${JSON.stringify(this.props)}`);
}
_renderItem = ({item}) => {
console.log(`item: ${JSON.stringify(item)}`);
return (
<DivisionManagementRow divisionId={item.DIVISION_ID} divisionName={item.DIVISION_NAME}
onAddTeam={() => {this._addTeam(item.DIVISION_ID)}}
onEdit={() => {this._editDivision(item.DIVISION_ID)}}
onDelete={() => {this._btnDeleteDivision(item.DIVISION_ID)}}/>
)};
render() {
console.log(`Props in render: ${JSON.stringify(this.props)}`);
return (
<View style={masterStyles.component}>
<View style={listPage.listArea}>
<FlatList
data={this.props.divisions}
renderItem={this._renderItem}
keyExtractor={(item) => item.DIVISION_ID.toString() } />
</View>
<View style={listPage.bottomButtonArea}>
<PortableButton defaultLabel="Add Division"
disabled={false}
onPress={() => {this._addDivision()}}
onLongPress={() => {}}
style={bigButtonStyles} />
</View>
</View>
);
}
}
function mapStateToProps(state) {
console.log(`State: ${JSON.stringify(state)}`);
return {
programId: state.programId,
divisions: state.divisions,
isLoading: state.isLoadingDivisions
}
}
function mapDispatchToProps(dispatch) {
return {
getAllDivisions: (programId) => dispatch(loadDivisions(programId))
};
}
export default withNavigation(connect(mapStateToProps, mapDispatchToProps)(DivisionManagmementScreen));
divisionActions.js
import {query2} from "./../../util/DbUtils";
export const DIVISIONS_LOADING = "DIVISIONS_LOADING";
export const DIVISIONS_BY_PROGRAM = "DIVISIONS_BY_PROGRAM";
export const DIVISIONS_POPULATE = "DIVISIONS_POPULATE";
export function loadDivisions(programId) {
console.log(`loadDivisions, programId: ${JSON.stringify(programId)}`);
let result = (dispatch) => {
dispatch(isLoadingDivisions(true));
query2("SELECT * FROM DIVISION WHERE DIVISION_PROGRAM_ID = ?", [programId])
.then((queryResults) => {
console.log("Query results: " + JSON.stringify(queryResults));
return queryResults;
}).then((queryResults) => {
dispatch(isLoadingDivisions(false));
return queryResults;
}).then((queryResults) => {
console.log("Dispatching query results " + JSON.stringify(queryResults));
dispatch(populateDivisions(queryResults.result));
//return queryResults;
});
}
return result;
}
export function isLoadingDivisions(value) {
console.log(`Entered isLoadingDivisions, value: ${value}`);
return {
type: DIVISIONS_LOADING,
payload: value
}
}
export function getDivisionsByProgram(programId) {
return {
type: DIVISIONS_BY_PROGRAM,
payload: programId
}
}
export function populateDivisions(divisions) {
console.log(`populateDivisions(), divisions: ${JSON.stringify(divisions)}`);
return {
type: DIVISIONS_POPULATE,
payload: divisions
}
}
divisionReducer.js
import { DIVISIONS_LOADING, DIVISIONS_BY_PROGRAM } from "../actions/divisionActions";
import {loadDivisions} from "../actions/divisionActions";
export function isLoadingDivisions(state = false, action) {
console.log(`Entered isLoadingDivisions: ${JSON.stringify(action)}`);
switch (action.type) {
case DIVISIONS_LOADING:
return action.payload;
default:
return state;
}
}
export function divisions(state = [], action) {
console.log("Entered divisions()");
switch (action.type) {
case DIVISIONS_BY_PROGRAM:
let divisions = loadDivisions(action.payload);
console.log(`Divisions in divisions(): ${JSON.stringify(divisions)}`);
return divisions;
default:
return state;
}
}
reducers\index.js
import {combineReducers} from "redux";
import {isLoadingDivisions, divisions} from "./divisionReducer";
export default rootReducer = combineReducers({
isLoadingDivisions,
divisions
});
What should be happening here is that state should have an entry:
{ divisions: // populated array }
Instead, I'm getting this:
{ divisions: [] }
(The second one corresponds with the initial state, so maybe it's being overwritten?)
So I have one, or potentially two, problems with this code:
It's executing twice according to the logs
the divisions array in the state is not populating.
Can anyone help?

Related

dispatch doesn't call reducer React Native

I create a component (AlertChild) to show alerts using redux in React-Native.
Currently when I call the dispatch from another component(another class)and this return, then the alertchild shows the message ok, but when I call the dispatch from the same component (Login), the alert is not showed and I can verify than the reducer (AlertReducer) is not called because the console.log() (in class AlertReducer) shows nothing.
AlertReducer:
export function AlertReducer(state = {}, action: any) {
console.log("Dispatch: " + action);
switch (action.type) {
case "INFO":
return {
alert: {
type: "info",
message: action.message,
},
};
case "DANGER":
return {
alert: {
type: "danger",
message: action.message,
},
};
case "CLEAR":
return {};
default:
return state;
}
}
AlertActions:
function showInfo(message: string) {
return {
type: "INFO",
message,
};
}
function showDanger(message: string) {
return {
type: "DANGER",
message,
};
}
function clear() {
return { type: "CLEAR" };
}
export const AlertActions = {
showInfo,
showDanger,
clear,
};
AlertChild:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Toast } from "native-base";
import { AlertActions } from "../Store/Actions/AlertActions";
const AlertChild = (visible = true) => {
const alert = useSelector((state) => state.alert.alert);
const dispatch = useDispatch();
useEffect(() => {
ShowAlert();
}, [visible, dispatch]);
function ShowAlert() {
if (visible && alert !== undefined && Object.keys(alert).length) {
Toast.show({
text: alert.message,
buttonText: "X",
type: alert.type,
duration: alert.type === "danger" ? 60000 : 6000,
});
dispatch(AlertActions.clear());
}
}
return <></>;
};
export default AlertChild;
Login:
import React, { useState, useEffect, useContext } from "react";
import { Text, TextInput, View, Image } from "react-native";
import { Button, Spinner } from "native-base";
import styles from "./Styles/LoginStyles";
import { useDispatch } from "react-redux";
import AlertChild from "../Components/AlertChild";
import { AlertActions } from "../Store/Actions/AlertActions";
const Login = (props: any) => {
const { navigation } = props;
const dispatch = useDispatch();
useEffect(() => {
dispatch(AlertActions.clear());
}, [dispatch]);
async function Test() {
dispatch(AlertActions.showInfo("Hello"));
}
return (
<View style={styles.container}>
<Button onPress={async () => await Test()}>
<Text>Test</Text>
</Button>
<AlertChild {...props} />
</View>
);
};
export default Login;
Why the alert message is not displayed immediately?
I just needed to put in AlertChild the const "alert" (selector) in the useEffect:
useEffect(()=>{
ShowAlert()
},[visible, alert, dispatch])

React-Native Redux unable to set state

I use Redux to set three Counters for my navigation bar. I can access the values in initial state. But I am unable to change the values. My Reducer looks like this:
const SET_FREUNDE = 'SET_FREUNDE';
const SET_CHATS = 'SET_CHATS';
const SET_VOTES = 'SET_VOTES';
export function setFreunde(value) {
return {
type: SET_FREUNDE,
value,
}
}
export function setChats(value) {
return {
type: SET_CHATS,
value,
}
}
export function setVotes(value) {
return {
type: SET_VOTES,
value,
}
}
const defaults =
{
countervotes: 2,
counterchats: 1,
counterfreunde: 1
};
function counter(state=defaults, action) {
switch (action.type) {
case SET_FREUNDE:
return {...state,counterfreunde: action.value}
case SET_CHATS:
return {...state,counterchats: action.value}
case SET_VOTES:
return {...state,countervotes: action.value}
default:{
return state;
}
}
}
export default counter;
Now I want to set the counters on a other screen:
import * as React from "react";
import { Image, StyleSheet,... } from "react-native";
...
import {connect} from 'react-redux';
import { setChats, setFreunde, setVotes } from '../redux/counter';
class ... extends React.Component<{}, State> {
constructor(props) {
super(props);
}
...
render(){
return(
<SafeAreaView style={styles.container}>
<Button onPress={() => setFreunde(2)}/>
...
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
...
});
const mapDispatchToProps = (dispatch, ownProps) => ({
setFreunde: () => {
const { value } = ownProps
dispatch(setFreunde(value))
},
setChats: () => {
const { value } = ownProps
dispatch(setChats(value))
},
setVotes: () => {
const { value } = ownProps
dispatch(setVotes(value))
}
})
export default connect( null, mapDispatchToProps )(NavStack)
If I log the setFreunde(2) the console says the following:
Object { "type": "SET_FREUNDE", "value": 2, }
However, the value does not change which I retrieve in my App.tsx as follows:
const counterfreunde = useSelector((state)=>state.counterfreunde);
What is my mistake?
Problem: Not Dispatching Action
<Button onPress={() => setFreunde(2)}/>
This code right here is just calling the action creator setFreunde. It does not dispatch it.
Your mapDispatchToProps function adds a prop setFreunde to your component which takes no arguments and dispatches the action with the value from props.value. You are not using this prop. You are just using the action creator. You need to call the function from props:
<Button onPress={this.props.setFreunde} title="Set Freunde" />
That fixes the functionality. You do need to add a lot of annotations if you are trying to use typescript here. It's easier with function components!
import { createStore } from "redux";
import { Provider, connect } from "react-redux";
import React, { Dispatch } from "react";
import { SafeAreaView, Button, Text } from "react-native";
const SET_FREUNDE = "SET_FREUNDE";
const SET_CHATS = "SET_CHATS";
const SET_VOTES = "SET_VOTES";
export function setFreunde(value: number) {
return {
type: SET_FREUNDE,
value
};
}
export function setChats(value: number) {
return {
type: SET_CHATS,
value
};
}
export function setVotes(value: number) {
return {
type: SET_VOTES,
value
};
}
const defaults = {
countervotes: 2,
counterchats: 1,
counterfreunde: 1
};
type State = typeof defaults;
type Action = ReturnType<typeof setVotes | typeof setChats | typeof setFreunde>;
function counter(state: State = defaults, action: Action): State {
switch (action.type) {
case SET_FREUNDE:
return { ...state, counterfreunde: action.value };
case SET_CHATS:
return { ...state, counterchats: action.value };
case SET_VOTES:
return { ...state, countervotes: action.value };
default: {
return state;
}
}
}
const store = createStore(counter);
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
ownProps: OwnProps
) => ({
setFreunde: () => {
const { value } = ownProps;
dispatch(setFreunde(value));
},
setChats: () => {
const { value } = ownProps;
dispatch(setChats(value));
},
setVotes: () => {
const { value } = ownProps;
dispatch(setVotes(value));
}
});
const mapStateToProps = (state: State) => ({
freund: state.counterfreunde
});
interface OwnProps {
value: number;
}
type Props = OwnProps &
ReturnType<typeof mapDispatchToProps> &
ReturnType<typeof mapStateToProps>;
class NavStack extends React.Component<Props> {
render() {
return (
<SafeAreaView>
<Text>Freunde Value: {this.props.freund}</Text>
<Button onPress={this.props.setFreunde} title="Set Freunde" />
</SafeAreaView>
);
}
}
const Test = connect(mapStateToProps, mapDispatchToProps)(NavStack);
export default function App() {
return (
<Provider store={store}>
<Test value={5} />
</Provider>
);
}
Code Sandbox Demo

React Native not able to dispatch action to reducer

I have Problem in dispatching an action and is suppose to update my state in redux.
This is my Homepage component. I am able to get the current state of the calculator balance.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { Text, ScrollView, Button } from "react-native";
import * as reducerActions from "../../store/actions/calculator";
function Homepage() {
const balance = useSelector((state) => state.calculator.balance);
const dispatch = useDispatch();
console.log(balance);
return (
<ScrollView>
<Text>Balance:{balance}</Text>
<Button
title="Add to Cart"
onPress={() => {
dispatch(reducerActions.DepositMoney(10));
}}
/>
</ScrollView>
);
}
export default Homepage;
This is my action component: The issue is that it doesn't call my reducer. It logs the value of 10 when I press on the button.
export const DEPOSIT = "DEPOSIT";
export const DepositMoney = (amount) => {
return { type: DEPOSIT, payload: amount };
};
This is my reducer component:
import { DEPOSIT } from "../actions/calculator";
const initialState = {
balance: 0,
};
export default (state = initialState, action) => {
switch (action.Type) {
case DEPOSIT:
console.log("reducer");
console.log(action.payload);
return { balance: state.balance + action.payload };
case "WITHDRAW":
return { balance: state.balance - action.payload };
}
return state;
};
And this is how i set up the redux in my app.js
import React, { useState } from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { AppLoading } from "expo";
import * as Font from "expo-font";
import ReduxThunk from "redux-thunk";
import productsReducer from "./store/reducers/products";
import ShopNavigator from "./navigation/ShopNavigator";
import calculatorReducer from "./store/reducers/calculator";
import { composeWithDevTools } from "redux-devtools-extension";
const rootReducer = combineReducers({
products: productsReducer,
calculator: calculatorReducer,
});
//composewithdevtools should be taken out for production
//const store = createStore(rootReducer, composeWithDevTools());
const store = createStore(
rootReducer,
composeWithDevTools(),
applyMiddleware(ReduxThunk)
);
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);
}}
/>
);
}
return (
<Provider store={store}>
<ShopNavigator />
</Provider>
);
}
#Christian is right but one thing is missing.
Must copy state before making change in it.
You can also do it like.
export default (state = initialState, {type,payload}) => {
switch (type) {
case DEPOSIT:
console.log("reducer");
console.log(payload);
return { ...state, balance: state.balance + payload };
case "WITHDRAW":
return {...state, balance: state.balance - payload };
}
return state;
};
It was something silly. The following line was wrong:
switch (action.Type) {
It should be:
switch (action.type) {

mapStateToProps does not re-render the component after state change

I've been working with React/React-Native for a while now, but I'm new to Redux and I cannot find the problem. I have a RESTFull API and two main modules: the service model and the price model. Once the admin user adds a new service the user can also associate a price for that service. The problema is that when I add a service (in the NewServiceScreen) my code dispatches an action to change the redux store and therefore update the service list on the NewPriceScreen for the user to associate a price with the service that was just added.
NewPriceScreen.js
function mapStateToProps(state){
return {
newPrice: state.newPriceReducer
}
}
// Exports the connected NewPriceScreen
export default connect(mapStateToProps)(NewPriceScreen);
NewServiceScreen
...
handleSubmit = async() => {
const { descricao } = this.props.newService;
const userToken = await AsyncStorage.getItem('token');
axios.post('/estabelecimento/servicos/',{descricao: descricao}, {
headers: {
"Authorization": `Token ${userToken}`
}
})
.then(res => {
Alert.alert(
'Deu tudo certo :)',
'Dados salvos com sucesso !',
);
console.log(res.data);
this.props.dispatch({type: 'addService', newService: res.data});
})
.catch(error =>{
Alert.alert(
'Ops ! Algo aconteceu :(',
error.message,
);
})
}
...
const mapStateToProps = state => ({
newService: state.newService
});
export default connect(mapStateToProps)(NewServiceScreen);
Reducers.js
const initialState = {
servicos: [],
// Database variables
servico: 0,
duracao: '',
custo: '',
comissao: ''
}
export default function newPriceReducer(state = initialState, action){
// console.log("My state")
// console.log(state);
switch(action.type){
case 'setState': {
return {
...state,
servico: action.descricao,
duracao: action.duracao,
custo: action.custo,
comissao: action.comissao
}
}
case "ADD_SERVICES": {
// console.log("Servicos");
const newState = {
...state,
servicos: action.servicos
}
// console.log(newState);
// console.log(newState === initialState)
return newState;
}
case 'addService': {
// console.log("ADD Service");
servicos = state.servicos;
servicos.push(action.newService);
const newState = {
...state,
servicos: servicos
}
// console.log(newState);
// console.log(newState === initialState);
return newState
}
default:
return state;
}
}
const initialState = {
descricao: ''
}
export default function newServiceReducer(state = initialState, action){
switch(action.type){
case 'setDescricao': {
return {
...state,
descricao: action.descricao
}
}
default:
return state;
}
}
App.js
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunkMiddleware from 'redux-thunk'
import newServiceReducer from '../reducers/NewService';
import newPriceReducer from "../reducers/NewPrice";
import logger from 'redux-logger';
const mainReducer = combineReducers({
newService: newServiceReducer,
newPriceReducer
})
const store = createStore(mainReducer, applyMiddleware(logger));
export default store
import React from 'react';
import { Platform, StatusBar, StyleSheet, View, AsyncStorage, ImageBackground} from 'react-native';
import { AppLoading, Asset, Font, Icon } from 'expo';
import AppNavigator from './navigation/AppNavigator';
// Redux Stuff
import {Provider} from 'react-redux';
import AppStore from './store/App';
export default class App extends React.Component {
state = {
isLoadingComplete: false,
isLoggedIn: false,
};
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<Provider store={AppStore}>
<ImageBackground source={require('./assets/images/back.png')} style={{width: '100%', height: '100%'}}>
{Platform.OS === 'ios' && <StatusBar barStyle="default" />}
<AppNavigator />
</ImageBackground>
</Provider>
);
}
}
}
In lines:
servicos = state.servicos;
servicos.push(action.newService);
I was making some sort mutation. I just changed it to:
const newState = {
...state,
servicos: [...state.servicos, action.newService]
}

nothing fires after fetch data from DB react native

i have index.js contain's Tabs each tab render the same component (animate.js) but with different props, the code as the following:
index.js
categoryList.map((item,index) => {
if(item.head_category == category_id)
return (
<Tab heading={item.category_name} key={index}>
<Animate category_id={item.category_id}/>
</Tab>
)
});
in the animate.js i receive the category_id number and fetch the data using redux , and the data gets back
for first animate.js rendering nothing fires after the data returns but if i switch the tabs everything works grate
animate.js
import React from 'react';
import { InteractionManager, StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {LineDotsLoader} from 'react-native-indicator';
import {goodsAction} from './redux/actions'
class animate extends React.Component {
constructor(props) {
super(props);
this.state = {
category_id:'',
loading:true,
};
console.log('constructor');
}
componentDidMount(){
const { category_id, category_name } = this.props;
this.props.goodsAction({category_id}); // My fetching Action
console.log(`componentDidMount `);
}
componentWillReceiveProps(nextProps){
console.log(`componentWillReceiveProps`)
}
static getDerivedStateFromProps(props, state){
console.log(` getDerivedStateFromProps `);
return null;
}
componentDidUpdate(prevProps, prevState) {
console.log(` componentDidUpdate `);
}
renderPage() {
return (
<View style={{ flex: 1 }}>
// anything
</View>
);
}
render(props) {
console.log(`render`);
if (this.props.loading) {
// if redux still fetching return loading:true else return false
return(<View style={styles.container}><LineDotsLoader /></View>)}
return (
<View style={styles.container}>
{this.renderPage()} // or anything
</View>
);
}
}
const mapStateToProps = state => {
return {
error: state.goods.error,
loading: state.goods.loading,
goods: state.goods.goods
}
}
export default connect(mapStateToProps, { goodsAction })(animate);
my console image
Edit
and this is my goodsRedusers.js
import {
GOODS_LOADING_ATTEMPT,
GOODS_REFRESH_ATTEMPT,
GOODS_LOADED,
GOODS_FAILED
} from '../actions/types';
const INITIAL_STATE = { goods:[], loading: true, error: '', }
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case GOODS_LOADING_ATTEMPT://dispatch before connecting to db
return {...INITIAL_STATE, loading: true }
case GOODS_FAILED:
return {...INITIAL_STATE, loading: false, error: action.error }
case GOODS_LOADED://dispatch after data gets back
return {...INITIAL_STATE, loading: false, goods: action.goods }
default:
return state;
}
}
static getDerivedStateFromProps(props, state){
console.log(` getDerivedStateFromProps `);
return null;
}
Returning null won't update the state when props change, if that's what you want.
Try removing this code.