Unable to get information on a Reducer using Redux - react-native

I have a question with a Reducer, the problem is that I'm used to get data from an API which brings just Key Value Pairs something like:
{
"-MObNVR5e180MfhvtbRY": {
"altura": "179 cm",
"apellido": "Cuéllar De León",
"edad": 31,
"fecha_nacimiento": "20/10/1989",
"imagen": "https://firebasestorage.googleapis.com/v0/b/alianzafc2021.appspot.com/o/yimmi%20cuellar.jpg?alt=media&token=144fa106-eabb-40c8-83b6-2e4b45034eda",
"iso_code": "SV",
"lugar_nacimiento": "San Julián",
"nacionalidad": "El Salvador",
"nombre_completo": "Yimmy Rodrigo Cuéllar De León",
"nombre_corto": "Yimmy Cuéllar",
"nombres": "Yimmy Rodrigo",
"numero": 1,
"pais": "El Salvador",
"peso": "66 kg",
"player_id": 108911,
"posicion": "Portero"
},
}
Now I need to get the data from an API that has Arrays within the Array like this:
{
"api": {
"results": 13,
"players": [
{
"player_id": 79308,
"player_name": "Felipe Ponce Ramírez",
"firstname": "Felipe",
"lastname": "Ponce Ramírez",
"number": null,
"position": "Midfielder",
"age": 32,
"birth_date": "29/03/1988",
"birth_place": "Ciudad Lerdo",
"birth_country": "Mexico",
"nationality": "Mexico",
"height": "177 cm",
"weight": "67 kg",
"injured": null,
"rating": null,
"team_id": 4299,
"team_name": "Alianza",
"league_id": 2979,
"league": "Primera Division",
"season": "2020-2021",
"captain": 0,
"shots": {
"total": 0,
"on": 0
},
"goals": {
"total": 4,
"conceded": 0,
"assists": 0,
"saves": 0
},
"passes": {
"total": 0,
"key": 0,
"accuracy": 0
},
"tackles": {
"total": 0,
"blocks": 0,
"interceptions": 0
},
"duels": {
"total": 0,
"won": 0
},
"dribbles": {
"attempts": 0,
"success": 0
},
"fouls": {
"drawn": 0,
"committed": 0
},
"cards": {
"yellow": 2,
"yellowred": 0,
"red": 0
},
"penalty": {
"won": 0,
"commited": 0,
"success": 0,
"missed": 0,
"saved": 0
},
"games": {
"appearences": 6,
"minutes_played": 194,
"lineups": 2
},
"substitutes": {
"in": 4,
"out": 2,
"bench": 0
}
},
}
Someone told me that I will need to create a Data Model for each SubArray so I did and I used it in my Action File like this:
export const fetchEstadistica = player_id => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
`https://api-football-v1.p.rapidapi.com/v2/players/player/${player_id}.json`,
{
method: 'GET',
headers: new Headers({
'x-rapidapi-key': //Here Goes My API Key which I keep for Security Reasons,
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'useQueryString': 'true'
})
}
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadesApiResult = [];
console.log(resData);
//Arrays de la Estadistica del Jugador
const loadedEstadistica = [];
const loadedCards = [];
const loadedGoals = [];
const loadedGames = [];
for (const key in resData) {
loadesApiResult.push(
new ResultadoEstadistica(
key,
resData[key].results,
resData[key].players
)
);
}
const apiData = loadesApiResult.players;
for (const key in apiData){
loadedEstadistica.push(
new PlayerEstadistica (
apiData[key].player_id,
apiData[key].player_name,
apiData[key].firstname,
apiData[key].lastname,
apiData[key].number,
apiData[key].position,
apiData[key].age,
apiData[key].birth_date,
apiData[key].birth_place,
apiData[key].birth_country,
apiData[key].nationality,
apiData[key].height,
apiData[key].weight,
apiData[key].injured,
apiData[key].rating,
apiData[key].team_id,
apiData[key].team_name,
apiData[key].league_id,
apiData[key].league,
apiData[key].season,
apiData[key].captain,
apiData[key].shots,
apiData[key].goals,
apiData[key].passes,
apiData[key].duels,
apiData[key].dribbles,
apiData[key].fouls,
apiData[key].cards,
apiData[key].penalty,
apiData[key].games,
apiData[key].substitutes,
)
);
}
const playerDataGames = loadedEstadistica.games;
for (const key in playerDataGames){
loadedGames.push(
new Games(
playerDataGames[key].apperences,
playerDataGames[key].minutes_played,
playerDataGames[key].lineups
)
);
};
const playerDataGoals = loadedEstadistica.goals;
for (const key in playerDataGoals){
loadedGoals.push(
new Goals(
playerDataGoals[key].total,
playerDataGoals[key].conceded,
playerDataGoals[key].assists,
playerDataGoals[key].saves
)
);
};
const playerDataCards = loadedEstadistica.cards;
for (const key in playerDataCards){
loadedCards.push(
new Cards(
playerDataCards[key].yellow,
playerDataCards[key].yellowred,
playerDataCards[key].red
)
);
};
dispatch({ type: SET_ESTADISTICA, estadistica: loadedEstadistica, goles: loadedGoals, juegos: loadedGames, tarjetas: loadedCards });
} catch (err) {
throw err;
}
};
};
Then I get this into my Readucer with the Type:
import { SET_JUGADORES, SET_ESTADISTICA } from "../actions/jugadores";
const initialState = {
availablePlayers: [],
estadistica: [],
playerGoals: [],
playerCards: [],
playerGames: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_JUGADORES:
return {
availablePlayers: action.players,
};
case SET_ESTADISTICA:
return{
estadistica: estadistica,
// playerGoals: action.goles,
// playerCards: action.tarjetas,
// playerGames: action.juegos
};
}
return state;
};
Finally, I go and call the Reducer from:
import React, {useState, useCallback, useEffect} from 'react';
import { ScrollView, Text, Image, StyleSheet, View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
const ProductDetailScreen = props => {
const playerId = props.route.params.id;
const estadId = props.route.params.statId;
const selectedPlayer = useSelector(state => state.jugadores.availablePlayers.find(prod => prod.id === playerId));
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const goles = useSelector(state => state.jugadores.estadistica);
const dispatch = useDispatch();
const loadEstad = useCallback (async (estadId) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(estadId));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
return (
<ScrollView>
<Image style={styles.image} source={{ uri: selectedPlayer.imagen }} />
<View style={styles.dataContainer}>
<Text style={styles.description}>Numero: <Text style={styles.subtitle}>{selectedPlayer.numero}</Text></Text>
<Text style={styles.description}>Nombre Completo: <Text style={styles.subtitle}>{selectedPlayer.nombre_completo}</Text></Text>
<Text style={styles.description}>Posicion: <Text style={styles.subtitle}>{selectedPlayer.posicion}</Text> </Text>
<Text style={styles.description}>Edad: <Text style={styles.subtitle}>{selectedPlayer.edad}</Text></Text>
<Text style={styles.description}>Nacionalidad: <Text style={styles.subtitle}>{selectedPlayer.nacionalidad}</Text></Text>
</View>
</ScrollView>
);
};
export const screenOptions = navData => {
return {
headerTitle: navData.route.params.nombre,
}
};
const styles = StyleSheet.create({
image: {
width: '100%',
height: 300,
},
subtitle: {
fontSize: 16,
textAlign: 'justify',
marginVertical: 20,
fontWeight:'normal',
},
description: {
fontSize: 16,
textAlign: 'center',
marginVertical: 20,
fontWeight: 'bold',
},
dataContainer:{
width: '80%',
alignItems: 'center',
marginHorizontal: 40,
},
actions: {
marginVertical: 10,
alignItems: 'center',
},
});
export default ProductDetailScreen;
However when I go to watch the Terminal I see that when I do the console.log(goles) which receives the slice of the Reducer I get Undefined.
I'm not sure what am I doing Wrong, any ideas?
Kind Regards

Try this
import { SET_JUGADORES, SET_ESTADISTICA } from "../actions/jugadores";
const initialState = {
availablePlayers: [],
estadistica: [],
playerGoals: [],
playerCards: [],
playerGames: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_JUGADORES:
return {
...state,
availablePlayers: action.players,
};
case SET_ESTADISTICA:
return{
...state,
estadistica: state.estadistica,
// playerGoals: action.goles,
// playerCards: action.tarjetas,
// playerGames: action.juegos
};
}
return state;
};

Related

React Native sound on interval skipping

I'm trying to get React Native to whistle 3 times before a timer, so for example, whistle 3 seconds in a row, then let the timer go, then whistle again, but for some reason it is only doing it twice, it's skipping the middle whistle and sometimes the last one.
I've tried mounting the sound before hand, reducing the sound duration to about .3 seconds, and it is still skipping some plays. I know I need to do some refactor on the timers, but I think at least playing the sound should work.
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
Vibration,
} from "react-native";
import React from "react";
import { StatusBar } from "expo-status-bar";
import { Audio } from "expo-av";
const screen = Dimensions.get("window");
let timeout: NodeJS.Timeout | undefined = undefined;
interface TimerComponentProps {
timeInSeconds?: number;
}
export const TimerComponent: React.FC<TimerComponentProps> = ({
timeInSeconds = 5,
}) => {
const [remaningSeconds, setRemainingSeconds] = React.useState(timeInSeconds);
const [isActive, setIsActive] = React.useState(false);
const [sound, setSound] = React.useState<Audio.Sound | undefined>(undefined);
const [shouldCount, setShouldCount] = React.useState(false);
const [counter, setCounter] = React.useState(3);
const { minutes, seconds } = React.useMemo(() => {
const minutes = Math.floor(remaningSeconds / 60);
const seconds = remaningSeconds % 60;
return { minutes, seconds };
}, [remaningSeconds]);
async function mountSound() {
try {
const { sound } = await Audio.Sound.createAsync(
require("../../assets/audio/Whistle.wav")
);
setSound(sound);
} catch (error) {
console.error(error);
}
}
async function playWhistle() {
if (sound) {
try {
await sound.playAsync();
} catch (error) {
console.error(error);
}
}
}
const endTimer = async () => {
try {
await playWhistle();
setIsActive(false);
} catch (error) {
console.error(error);
}
};
const startCounter = async () => {
await mountSound();
setShouldCount(true);
};
const resetTimer = () => {
if (timeout) {
clearTimeout(timeout);
} else {
timeout = setTimeout(() => {
setRemainingSeconds(timeInSeconds);
clearTimeout(timeout);
}, 1000);
}
};
React.useEffect(() => {
let counterInterval: NodeJS.Timer | undefined = undefined;
if (shouldCount) {
counterInterval = setInterval(() => {
try {
if (counter === 1) {
setCounter((counter) => counter - 1);
}
if (counter > 1) {
playWhistle();
Vibration.vibrate();
setCounter((counter) => counter - 1);
} else {
// Plays the whistle sound and vibrates the device
playWhistle();
Vibration.vibrate();
// Restarts the counter
setCounter(3);
setShouldCount(false);
// Starts the timer
setIsActive(true);
// Stops the counter
clearInterval(counterInterval);
}
} catch (error) {
console.error(error);
}
}, 1000);
} else if (!shouldCount && counter !== 0) {
clearInterval(counterInterval);
}
return () => clearInterval(counterInterval);
}, [shouldCount, counter]);
React.useEffect(() => {
let timerInterval: NodeJS.Timer | undefined = undefined;
if (isActive) {
timerInterval = setInterval(() => {
if (remaningSeconds === 1) {
setRemainingSeconds((remaningSeconds) => remaningSeconds - 1);
}
if (remaningSeconds > 1) {
setRemainingSeconds((remaningSeconds) => remaningSeconds - 1);
} else {
Vibration.vibrate();
endTimer();
resetTimer();
}
}, 1000);
} else if (!isActive && remaningSeconds === 0) {
resetTimer();
clearInterval(timerInterval);
}
return () => clearInterval(timerInterval);
}, [isActive, remaningSeconds]);
React.useEffect(() => {
return sound
? () => {
sound.unloadAsync();
setSound(undefined);
}
: undefined;
}, [sound]);
const parseTime = (time: number) => {
return time < 10 ? `0${time}` : time;
};
return (
<View style={styles.container}>
<StatusBar style="light" />
<Text style={styles.timerText}>{`${parseTime(minutes)}:${parseTime(
seconds
)}`}</Text>
<TouchableOpacity onPress={startCounter} style={styles.button}>
<Text style={styles.buttonText}>{isActive ? "Pause" : "Start"}</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#07121B",
alignItems: "center",
justifyContent: "center",
},
button: {
borderWidth: 10,
borderColor: "#B9AAFF",
width: screen.width / 2,
height: screen.width / 2,
borderRadius: screen.width / 2,
alignItems: "center",
justifyContent: "center",
},
buttonText: {
color: "#B9AAFF",
fontSize: 20,
},
timerText: {
color: "#fff",
fontSize: 90,
},
});
The issue was that expo-av leaves the audio file at it's end, so the next time you play it, nothing will sound because the file is already over, the way to fix it is pretty simple:
async function playWhistle() {
if (sound) {
try {
await sound.playAsync();
sound.setPositionAsync(0); // ADD THIS LINE
} catch (error) {
console.error(error);
}
}
}

Reusing custom TextInput component in react native?

I am using react native 0.68.5
When I call the renderReusableTextInput method in Main.js I am sending all the styles including the needed and unneeded styles. This can lead performance penalty.
So, is there a way that I can send the only needed styles to the renderReusableTextInput method.
I also want to know is the code reusing approach of mine correct or there are better ways.
I have the following code-logic structure:
(i) A Reusable component; It has only structure no style or state
(ii) A file that includes reusable methods
(iii) The Main component, which contains components, styles and states
ReusableTextInput.js
// import ...
const ReusableTextInput = forwardRef(({
inputName,
inputLabel,
inputValue,
secureTextEntry,
textInputContainerWarperStyle,
textInputContainerStyle,
textInputLabelStyle,
textInputStyle,
textInputHelperStyle,
textInputErrorStyle,
helperText,
inputError,
onFocus,
onChangeText,
onBlur,
}, inputRef) => {
return (
<View style={[textInputContainerWarperStyle]}>
<View style={[textInputContainerStyle]}>
<Text style={[textInputLabelStyle]}>
{inputLabel}
</Text>
<TextInput
ref={(elm) => inputRef[inputName] = elm}
style={[textInputStyle]}
value={inputValue}
secureTextEntry={secureTextEntry}
onChangeText={onChangeText}
onFocus={onFocus}
onBlur={onBlur}
/>
</View>
{
((inputRef[inputName]) && (inputRef[inputName].isFocused()) && (!inputValue))
?
<Text style={[textInputHelperStyle]}>
{helperText}
</Text>
:
null
}
{
((inputError) && (inputValue))
?
<Text style={[textInputErrorStyle]}>
{inputError}
</Text>
:
null
}
</View>
);
});
export default memo(ReusableTextInput);
reusableMethods.js
// import ...
const handleFocus = (state, setState, styles) => {
const stateData = { ...state };
stateData.styleNames.textInputContainer = {
...styles.textInputContainer,
...styles[`${stateData.name}ExtraTextInputContainer`],
...styles.textInputContainerFocus,
};
stateData.styleNames.textInputLabel = {
...styles.textInputLabel,
...styles[`${stateData.name}ExtraTextInputLabel`],
...styles.textInputLabelFocus,
};
stateData.styleNames.textInput = {
...styles.textInput,
...styles[`${stateData.name}ExtraTextInput`],
...styles.textInputFocus,
};
// other logics...
setState(stateData);
};
const handleChangeText = (state, setState, text) => {
const stateData = { ...state };
// individual validation
const schemaData = Joi.object().keys(stateData.validationObj); // I used Joi for validation
const inputData = { [stateData.name]: text };
const options = { abortEarly: false, errors: { label: false } };
const result = schemaData.validate(inputData, options);
// -----
stateData.error = (result.error) ? result.error.details[0].message : '';
stateData.value = text;
// other logics...
setState(stateData);
};
const handleBlur = (state, setState, styles) => {
const stateData = { ...state };
if (stateData.value) {
stateData.styleNames.textInputContainer = {
...styles.textInputContainer,
...styles[`${stateData.name}ExtraTextInputContainer`],
...styles.textInputContainerFocus,
...styles[`${stateData.name}ExtraTextInputContainerFocus`],
...styles.textInputContainerBlurText,
...styles[`${stateData.name}ExtraTextInputContainerBlurText`],
};
stateData.styleNames.textInputLabel = {
...styles.textInputLabel,
...styles[`${stateData.name}ExtraTextInputLabel`],
...styles.textInputLabelFocus,
...styles[`${stateData.name}ExtraTextInputLabelFocus`],
...styles.textInputLabelBlurText,
...styles[`${stateData.name}ExtraTextInputLabelBlurText`],
};
stateData.styleNames.textInput = {
...styles.textInput,
...styles[`${stateData.name}ExtraTextInput`],
...styles.textInputFocus,
...styles[`${stateData.name}ExtraTextInputFocus`],
...styles.textInputBlurText,
...styles[`${stateData.name}ExtraTextInputBlurText`],
};
}
else {
stateData.styleNames.textInputContainer = { ...styles.textInputContainer, ...styles[`${stateData.name}ExtraTextInputContainer`] };
stateData.styleNames.textInputLabel = { ...styles.textInputLabel, ...styles[`${stateData.name}ExtraTextInputLabel`] };
stateData.styleNames.textInput = { ...styles.textInput, ...styles[`${stateData.name}ExtraTextInput`] };
}
// other logics...
setState(stateData);
};
// other methods...
export const renderReusableTextInput = (
state,
setState,
inputRef,
styles,
// contains all the styles from Main component and here I am sending all the styles including the needed and unneeded styles. I want improvement here
) => {
return (
<ReusableTextInput
inputName={state.name}
inputLabel={state.label}
inputValue={state.value}
inputRef={inputRef}
secureTextEntry={state.secureTextEntry}
textInputContainerWarperStyle={{...styles.textInputContainerWarper, ...styles[`${state.name}ExtraTextInputContainerWarper`]}}
textInputContainerStyle={state.styleNames.textInputContainer}
textInputLabelStyle={state.styleNames.textInputLabel}
textInputStyle={state.styleNames.textInput}
textInputHelperStyle={{...styles.textInputHelper, ...styles[`${state.name}ExtraTextInputHelper`]}}
textInputErrorStyle={{...styles.textInputError, ...styles[`${state.name}ExtraTextInputError`]}}
helperText={state.helperText}
inputError={state.error}
onFocus={() => handleFocus(state, setState, styles)}
onChangeText={(text) => handleChangeText(state, setState, text)}
onBlur={() => handleBlur(state, setState, styles)}
/>
);
};
Main.js
// import Joi from 'joi';
// import { joiPasswordExtendCore } from 'joi-password';
// import { renderReusableTextInput } from ''; ...
const schema =
{
email: Joi.string().strict()
.case("lower")
.min(5)
.max(30)
.email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "org"] } })
.required(),
countryCode: // Joi.string()...,
phoneNumber: // Joi.string()...,
password: // Joi.string()...,
// ...
};
const Main = () => {
const { width: windowWidth, height: windowHeight, scale, fontScale } = useWindowDimensions();
const minimumWidth = (windowWidth <= windowHeight) ? windowWidth : windowHeight;
const styles = useMemo(() => currentStyles(minimumWidth), [minimumWidth]);
const [email, setEmail] = useState({
name: 'email', // unchangeable
label: 'Email', // unchangeable
value: '',
error: '',
validationObj: { email: schema.email }, // unchangeable
trailingIcons: [require('../../file/image/clear_trailing_icon.png')], // unchangeable
helperText: 'only .com, .net and .org allowed', // unchangeable
styleNames: {
textInputContainer: { ...styles.textInputContainer, ...styles.emailExtraTextInputContainer },
textInputLabel: { ...styles.textInputLabel, ...styles.emailExtraTextInputLabel },
textInput: { ...styles.textInput, ...styles.emailExtraTextInput },
},
});
const [phoneNumber, setPhoneNumber] = useState({
// ...
});
const [countryCode, setCountryCode] = useState({
});
const [password, setPassword] = useState({
});
// ...
const references = useRef({});
return (
<View style={[styles.mainContainer]}>
{
useMemo(() => renderReusableTextInput(email, setEmail, references.current, styles), [email, minimumWidth])
}
</View>
);
}
export default memo(Main);
const styles__575 = StyleSheet.create({
// 320 to 575
mainContainer: {
},
textInputContainerWarper: {
},
emailExtraTextInputContainerWarper: {
},
countryCodeExtraTextInputContainerWarper: {
},
phoneNumberExtraTextInputContainerWarper: {
},
passwordExtraTextInputContainerWarper: {
},
textInputContainer: {
},
emailExtraTextInputContainer: {
},
textInputContainerFocus: {
},
textInputContainerBlurText: {
},
textInputLabel: {
},
emailExtraTextInputLabel: {
},
textInputLabelFocus: {
},
textInputLabelBlurText: {
},
textInput: {
},
emailExtraTextInput: {
},
textInputFocus: {
},
textInputBlurText: {
},
textInputHelper: {
},
emailExtraTextInputHelper: {
},
textInputError: {
},
emailExtraTextInputError: {
},
// other styles...
});
const styles_576_767 = StyleSheet.create({
// 576 to 767
});
const styles_768_ = StyleSheet.create({
// 768; goes to 1024;
});
const currentStyles = (width, stylesInitial = { ...styles__575 }, styles576 = { ...styles_576_767 }, styles768 = { ...styles_768_ }) => {
let styles = {};
if (width < 576) {
// ...
}
else if ((width >= 576) && (width < 768)) {
// ...
}
else if (width >= 768) {
// ...
}
return styles;
};
I tried mentioned approach and want a better answer.
First, I think you want global styles, so that you can access it from anywhere and you can also be able to execute needed code.
Make sure you use useMemo, useCallback in right manner, for better performance.
Move all Schema and Styles and Methods of your screens inside the reusableMethods.js file (at most case). It will act like the controller of all screens of your app, also make a demo method which return a tiny component and this method execute another method, so that you can get styles for different dimentions(see below code)
Store only changeable and needed properties in state variables.
Is code reusing approach of yours, correct? I can't say about that. I will say that it depends on developer choice.
you can try like below:
reusableMethods.js
// import all schema, style variants, utility methods and other
let screenStyles = {};
const executeDimensionBasedMethods = (width, screenName) => {
if (screenName === 'a_screen_name') screenStyles[screenName] = currentStyles(width, otherParameter);
// else if()
// ...
};
export const renderDimension = (width, screenName) => {
executeDimensionBasedMethods(width, screenName);
return (
<Text style={{ width: 0, height: 0 }}></Text>
);
};
// all method definitions and logics
export const renderReusableTextInput = (
state,
setState,
inputRef,
screenName
) => {
return (
<ReusableTextInput
inputName={state.name}
inputLabel={state.label}
inputValue={state.value}
inputRef={inputRef}
secureTextEntry={state.secureTextEntry}
textInputContainerWarperStyle={{ ...screenStyles[screenName].textInputContainerWarper, ...screenStyles[screenName][`${state.name}ExtraTextInputContainerWarper`] }}
textInputContainerStyle={state.styleNames.textInputContainer || { ...screenStyles[screenName].textInputContainer }
textInputLabelStyle={state.styleNames.textInputLabel || { ...screenStyles[screenName].textInputLabel }
textInputStyle={state.styleNames.textInput || { ...screenStyles[screenName].textInput }
textInputHelperStyle={{ ...screenStyles[screenName].textInputHelper, ...screenStyles[screenName][`${state.name}ExtraTextInputHelper`] }}
textInputErrorStyle={{ ...screenStyles[screenName].textInputError, ...screenStyles[screenName][`${state.name}ExtraTextInputError`] }}
helperText={state.helperText}
inputError={state.error}
onFocus={() => handleFocus(state, setState, screenStyles[screenName])}
onChangeText={(text) => handleChangeText(state, setState, text)}
onBlur={() => handleBlur(state, setState, screenStyles[screenName])}
/>
);
};
Main.js
const Main = () => {
const { width: windowWidth, height: windowHeight} = useWindowDimensions();
const minimumWidth = (windowWidth <= windowHeight) ? windowWidth : windowHeight;
const [email, setEmail] = useState({
name: 'email', // unchangeable
label: 'Email', // unchangeable
value: '',
error: '',
trailingIcons: [require('../../file/image/clear_trailing_icon.png')], // unchangeable
helperText: 'only .com, .net and .org allowed', // unchangeable
styleNames: {
textInputContainer: undefined,
textInputLabel: undefined,
textInput: undefined,
},
});
// other states
const references = useRef({});
return (
<>
{
useMemo(() => renderDimension(minimumWidth), [minimumWidth])
}
<View style={{ // inline style}}>
{
useMemo(() => renderReusableTextInput(email, setEmail, references.current, 'nameofscreen'), [email, minimumWidth])
}
</View>
</>
}
export default memo(Main);

how can I access data from array inside array

I need to access an image inside 2 arrays, but I have an error to do a map inside another one any suggestions?
all data printed successfully but I need to access an image which is an array inside another array
I need to access an image inside 2 arrays, but I have an error to do a map inside another one any suggestions?
here is the code
`
import { StyleSheet, Text, View, FlatList, Image,Dimensions } from 'react-native'
import React, { useEffect, useState } from 'react'
import Artist from '../components/Artist'
import SearchHeader from '../components/SearchHeader';
import { useNavigation, useRoute } from "#react-navigation/native";
import { SafeAreaView } from 'react-native-safe-area-context';
import axios from 'axios';
import { Artistfu } from '../services.js/services';
const ArtistSearch = () => {
const route = useRoute()
let token = route.params.token
const navigation = useNavigation();
const [artistt, setArtistt] = useState([]);
const [error, setError] = useState('');
const [images, setImages] = useState([])
useEffect(() => {
Artistfu().then(artists => {
setArtistt(artists)
setImages(artists[0].images)
}).catch(err => {
setError(err);
});
}, []);
console.log(artistt)
return (
<View>
{
artistt.map((ar, i) => (
<View
key={i}
>
<Text>{ar.followers.total}</Text>
{images.map((img)=>(
<Image source={{ uri: img.images.url }} style={{ height: 210, width: Dimensions.get('window').width - 40, resizeMode: 'cover' }} />
))}
<Text >{ar.name}</Text>
<Text>{ar.popularity}</Text>
</View>
))
}
</View>
)
}
export default ArtistSearch
const styles = StyleSheet.create({
})
services file :
import axios from 'axios';
const apiUrl = 'https://api.spotify.com'
const token = 'query=remaster%2520track%3ADoxy%2520artist%3AMiles%2520Davis&type=artist&market=ES&locale=en-US%2Cen%3Bq%3D0.9&offset=5&limit=10'
export const Artistfu = async () => {
const resp = await axios.get(`${apiUrl}/v1/search?${token}`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
})
return resp.data.artists.items
};
`
and the data look like json:
`
{
"artists": {
"href": "https://api.spotify.com/v1/search?query=remaster%2520track%3ADoxy%2520artist%3AMiles%2520Davis&type=artist&market=ES&locale=en-US%2Cen%3Bq%3D0.9&offset=5&limit=10",
"items": [
{
"external_urls": {
"spotify": "https://open.spotify.com/artist/1MVFEpYdHtdV3k8Bgs79Pl"
},
"followers": {
"href": null,
"total": 404
},
"genres": [],
"href": "https://api.spotify.com/v1/artists/1MVFEpYdHtdV3k8Bgs79Pl",
"id": "1MVFEpYdHtdV3k8Bgs79Pl",
"images": [
{
"height": 640,
"url": "https://i.scdn.co/image/ab6761610000e5ebd1578a57ff89ac9aeed23863",
"width": 640
},
{
"height": 320,
"url": "https://i.scdn.co/image/ab67616100005174d1578a57ff89ac9aeed23863",
"width": 320
},
{
"height": 160,
"url": "https://i.scdn.co/image/ab6761610000f178d1578a57ff89ac9aeed23863",
"width": 160
}
],
"name": "DOXY",
"popularity": 14,
"type": "artist",
"uri": "spotify:artist:1MVFEpYdHtdV3k8Bgs79Pl"
},
]
}
}
`
The below should work
artists.items[0].images

Unable to acess a Function with parameters inside of UseCallback

I have this page (screen) that receives via Params an ID number, in this Screen, I try to call an Action Function from my Action (reducer) file and gets an API call, I thought I didn't get any information in the Array from that call, I believe that the issue was in the Call, but I put a Console log after the declaration on the Action Function, but it didn't print so I think it didn't access to that function, so I believe the issue is in the Call of that function via Dispatch.
I even tried to put a Breakpoint inside the UseEfect where I call the Function that calls the Dispatch Function but it never breaks I'm not sure where is the error, this is the Code:
Screen (where I suspect the issue is):
```import React, {useState, useCallback, useEffect} from 'react';
import { ScrollView, Text, Image, StyleSheet, View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
const ProductDetailScreen = props => {
const playerId = props.route.params.id;
const estadId = props.route.params.statId;
const selectedPlayer = useSelector(state => state.jugadores.availablePlayers.find(prod => prod.id === playerId));
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const goles = useSelector(state => state.jugadores.playerGoals);
const dispatch = useDispatch();
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
return (
<ScrollView>
<Image style={styles.image} source={{ uri: selectedPlayer.imagen }} />
<View style={styles.dataContainer}>
<Text style={styles.description}>Numero: <Text style={styles.subtitle}>{selectedPlayer.numero}</Text></Text>
<Text style={styles.description}>Nombre Completo: <Text style={styles.subtitle}>{selectedPlayer.nombre_completo}</Text></Text>
<Text style={styles.description}>Posicion: <Text style={styles.subtitle}>{selectedPlayer.posicion}</Text> </Text>
<Text style={styles.description}>Edad: <Text style={styles.subtitle}>{selectedPlayer.edad}</Text></Text>
<Text style={styles.description}>Nacionalidad: <Text style={styles.subtitle}>{selectedPlayer.nacionalidad}</Text></Text>
</View>
</ScrollView>
);
}
;
export const screenOptions = navData => {
return {
headerTitle: navData.route.params.nombre,
}
};
const
styles = StyleSheet.create({
image: {
width: '100%',
height: 300,
},
subtitle: {
fontSize: 16,
textAlign: 'justify',
marginVertical: 20,
fontWeight:'normal',
},
description: {
fontSize: 16,
textAlign: 'center',
marginVertical: 20,
fontWeight: 'bold',
},
dataContainer:{
width: '80%',
alignItems: 'center',
marginHorizontal: 40,
},
actions: {
marginVertical: 10,
alignItems: 'center',
},
});
export default ProductDetailScreen
;```
This is my Action File:
import ResultadoEstadistica from '../../models/estadistica/resultadoEstadistica';
import PlayerEstadistica from '../../models/estadistica/playerEstatisticData';
import Cards from '../../models/estadistica/cards';
import Games from '../../models/estadistica/games';
import Goals from '../../models/estadistica/goals';
export const SET_JUGADORES = 'SET_JUGADORES';
export const SET_ESTADISTICA = 'SET_ESTADISTICA';
export const fetchJugadores = () => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
'https://alianzafc2021-default-rtdb.firebaseio.com/jugadores.json'
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadedJugadores = [];
for (const key in resData) {
loadedJugadores.push(
new Jugador(
key,
resData[key].altura,
resData[key].apellido,
resData[key].edad,
resData[key].fecha_nacimiento,
resData[key].iso_code,
resData[key].imagen,
resData[key].lugar_nacimiento,
resData[key].nacionalidad,
resData[key].nombre_completo,
resData[key].nombre_corto,
resData[key].nombres,
resData[key].numero,
resData[key].pais,
resData[key].peso,
resData[key].player_id,
resData[key].posicion
)
);
}
dispatch({ type: SET_JUGADORES, players: loadedJugadores });
} catch (err) {
throw err;
}
};
}
export const fetchEstadistica = player_id => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
`https://api-football-v1.p.rapidapi.com/v2/players/player/${player_id}.json`,
{
method: 'GET',
headers: {
'x-rapidapi-key': Here goes my API KEY,
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'useQueryString': 'true'
}
}
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadesApiResult = [];
console.log('***Impresion desde la accion***');
console.log(resData);
console.log('***Fin de Impresionc***');
//Arrays de la Estadistica del Jugador
const loadedEstadistica = [];
const loadedCards = [];
const loadedGoals = [];
const loadedGames = [];
for (const key in resData) {
loadesApiResult.push(
new ResultadoEstadistica(
resData[key].results,
resData[key].players
)
);
}
const apiData = loadesApiResult.players;
for (const key in apiData) {
loadedEstadistica.push(
new PlayerEstadistica(
apiData[key].player_id,
apiData[key].player_name,
apiData[key].firstname,
apiData[key].lastname,
apiData[key].number,
apiData[key].position,
apiData[key].age,
apiData[key].birth_date,
apiData[key].birth_place,
apiData[key].birth_country,
apiData[key].nationality,
apiData[key].height,
apiData[key].weight,
apiData[key].injured,
apiData[key].rating,
apiData[key].team_id,
apiData[key].team_name,
apiData[key].league_id,
apiData[key].league,
apiData[key].season,
apiData[key].captain,
apiData[key].shots,
apiData[key].goals,
apiData[key].passes,
apiData[key].duels,
apiData[key].dribbles,
apiData[key].fouls,
apiData[key].cards,
apiData[key].penalty,
apiData[key].games,
apiData[key].substitutes,
)
);
}
const playerDataGames = loadedEstadistica.games;
for (const key in playerDataGames) {
loadedGames.push(
new Games(
playerDataGames[key].apperences,
playerDataGames[key].minutes_played,
playerDataGames[key].lineups
)
);
};
const playerDataGoals = loadedEstadistica.goals;
for (const key in playerDataGoals) {
loadedGoals.push(
new Goals(
playerDataGoals[key].total,
playerDataGoals[key].conceded,
playerDataGoals[key].assists,
playerDataGoals[key].saves
)
);
};
const playerDataCards = loadedEstadistica.cards;
for (const key in playerDataCards) {
loadedCards.push(
new Cards(
playerDataCards[key].yellow,
playerDataCards[key].yellowred,
playerDataCards[key].red
)
);
};
dispatch({ type: SET_ESTADISTICA, estadistica: loadesApiResult, goles: loadedGoals, juegos: loadedGames, tarjetas: loadedCards });
} catch (err) {
throw err;
}
};
};```
Finally this is my Redux Reducer just incase:
import { SET_JUGADORES, SET_ESTADISTICA } from "../actions/jugadores";
const initialState = {
availablePlayers: [],
estadistica: [],
playerGoals: [],
playerCards: [],
playerGames: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_JUGADORES:
return {
...state,
availablePlayers: action.players,
};
case SET_ESTADISTICA:
return{
...state,
estadistica: estadistica,
playerGoals: action.goles,
playerCards: action.tarjetas,
playerGames: action.juegos
};
}
return state;
};
Sorry for the Format but is giving me some issues; Any Ideas what my Be the Problem?
Thank you.
there are a few issues with your screen code, so i recommend simplifying the logic to make sure it works before adding anything thing else.
replace this:
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
with this:
useEffect(()=>{
if (estadId) dispatch(userActions.fetchEstadistica(estadId));
},[estadId]);
Assuming your reducer/action code is correct, then this should call the api everytime the params estadId changes. The loading/refresh should be set in the reducer instead on the screen component.
a few things to keep in mind:
Don't await for dispatch.
console.log on state variables outside of promise resolving code block won't work.
This below won't work. instead, you should set the loading variable as a redux variable that gets updated after the API comes back with data.
loadEstad(estadId).then(() => {
setIsLoading(false);
});

How do I get access to to this.props.navigation if I'm on my RootPage?

I need to pass this.props.navigation to a helper function. However, I'm currently sitting on the root page, which initially creates the stackNavigator. So far I've tried importing Navigator, NavigatorActions and withNavigation from react-navigation and then sending it/them along as a parameter to my helper function, but I keep getting the error message: cannot read property 'state' of undefined.
Here is my RootPage code that is relevant:
/**
* RootPage has the Navigator. So it controls navigation.
*/
import React, { Component } from 'react';
import {
Platform,
BackHandler,
View,
Animated,
StyleSheet,
Image,
TouchableWithoutFeedback,
TouchableOpacity,
Alert
} from 'react-native';
import { connect } from 'react-redux';
import { BoxShadow } from 'react-native-shadow';
import { NavigationActions } from 'react-navigation';
import { vsprintf } from 'sprintf-js';
import RootNavigator from './RootNavigator';
//Test Component
import TestPageFactory from '../TestPage/TestPageFactory';
// Components
import XXBluetoothManager, { XXBluetoothEventEmitter } from '../../nativeComponents/XXBluetooth/XXBluetooth';
import XXText from '../../components/XXText/XXRobotoText';
import XXSyncXXerlay from '../../components/XXSyncXXerlay/XXSyncXXerlay';
// Actions
import { logout } from '../../actions/user';
import { closeSyncModal, toggleRootMenu, openRootMenu, closeRootMenu } from '../../actions/page';
// Utils
import { LAYOUTINFO } from '../../utils/layoutinfo';
import { generateElevationStyle } from '../../utils/util';
import { NavUtils } from '../../utils/navutils';
import { StatusBarManager } from '../../utils/statusbarmanager';
// Config
import { DEVICEINFO } from '../../config';
// Localization
import I18n from '../../localization/i18n';
import { aDeviceHasBeenConnectedFunc } from '../../actions/device';
// Constants
const DEFAULT_PROFILE_IMAGE = require('../../images/profile-image/user.png');
const HEIGHT_RATIO = DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 0.5 : (DEVICEINFO.SCREEN_HEIGHT >= 667 ? (DEVICEINFO.SCREEN_HEIGHT / 667) : 0.7);
class RootPage extends Component {
static getDerivedStateFromProps(nextProps, prevState) {
let shouldLoadingBeVisible = nextProps.pageStatus && nextProps.pageStatus.isUpdateRequested ? true : false;
if (!nextProps.profile && shouldLoadingBeVisible === prevState.loadingVisible) {
return null;
}
return {
profileImage: nextProps.profile && nextProps.profile.profileImage ?
{ uri: nextProps.profile.profileImage } : DEFAULT_PROFILE_IMAGE,
profileName: nextProps.profile && nextProps.profile.profileName ? nextProps.profile.profileName : '',
loadingVisible: shouldLoadingBeVisible,
};
}
constructor(props) {
super(props);
this.routeStatusBarStyle = 'dark-content';
this.state = {
animatedPosition: new Animated.ValueXY(0, 0),
profileImage: DEFAULT_PROFILE_IMAGE,
profileName: '',
deviceAddress: undefined,
isMenuVisible: false,
loggingOut: false,
device: undefined,
};
}
componentDidMount() {
if (Platform.OS === 'android') {
this.initAndroidHardwareBackEvent();
}
// this.initializeBluetooth();
}
componentWillUnmount() {
this.destroyBluetooth();
}
componentDidUpdate(prevProps, prevState) {
if (this.state.loggingOut) {
if (this.props.signinStatus && !this.props.signinStatus.isLoginSuccess) {
this.setState({ loggingOut: false }, () => {
this.onPressMenu('EntryPage');
});
}
}
if (this.props.pageStatus.isMenuVisible != null && prevState.isMenuVisible !== this.props.pageStatus.isMenuVisible) {
this.setState({ isMenuVisible: this.props.pageStatus.isMenuVisible }, () => {
this.onUpdateMenu(this.props.pageStatus.isMenuVisible);
});
}
// console.log('what are the prev state here? ", ', prevState)
/*
NOTE: We need to know when an item has been connected, if an item has not been connected, we should
make the hearing test available
*/
}
render() {
return (
<View style={styles.rootView}>
{this.renderRootMenu()}
<Animated.View style={[styles.animatedView, { left: this.state.animatedPosition.x, top: this.state.animatedPosition.y }]}>
{this.renderXXerlay()}
{this.renderNavigator()}
</Animated.View>
{this.renderSyncXXerlay()}
</View>
);
}
updateStatusBarStyle(pageId) {
let newStatusBarStyle = 'dark-content';
if (pageId === 'EntryPage') {
newStatusBarStyle = 'light-content';
}
if (newStatusBarStyle !== this.routeStatusBarStyle) {
this.routeStatusBarStyle = newStatusBarStyle;
StatusBarManager.setStyle(newStatusBarStyle, false)
}
}
renderRootMenu() {
return (
<View style={styles.menuView}>
{this.renderMenuTopArea()}
{this.renderMenuBottomArea()}
</View>
)
}
renderMenuTopArea() {
if (DEVICEINFO.IS_ANDROID && (Platform.Version === 19 || Platform.Version === 20)) {
return this.renderMenuTopAreaKitKat();
}
return this.renderMenuTopAreaDefault();
}
renderMenuTopAreaDefault() {
return (
<View style={[styles.menuTopArea, generateElevationStyle(1.5)]}>
{this.renderProfileImage()}
</View>
)
}
renderMenuTopAreaKitKat() {
let shadowOptions = {
width: DEVICEINFO.SCREEN_WIDTH,
height: (DEVICEINFO.SCREEN_HEIGHT * 0.35) + 2,
color: '#000',
border: 2,
opacity: 0.05,
radius: 1,
y: -2.5,
};
return (
<BoxShadow setting={shadowOptions}>
<View style={styles.menuTopArea}>
{this.renderProfileImage()}
</View>
</BoxShadow>
)
}
renderMenuBottomArea() {
return (
<View style={styles.menuBottomArea}>
{this.renderMenuContents()}
</View>
)
}
renderMenuContents() {
const menuData = this.generateMenuData();
let renderOutput = [];
for (let i = 0, len = menuData.length; i < len; i++) {
if (this.state.deviceAddress && menuData[i].onlyNotConnected) {
continue;
}
let extraStyle = {};
if (i === 0) {
let extraStyleMarginTop = DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 16 : 26;
extraStyle['marginTop'] = extraStyleMarginTop * LAYOUTINFO.DESIGN_HEIGHT_RATIO;
}
renderOutput.push(
<TouchableOpacity
key={menuData[i].text}
activeOpacity={0.8}
underlayColor='transparent'
onPress={() => { menuData[i].onPressMenu() }}
style={[styles.menuButtonTouch, extraStyle]}>
<View style={styles.menuButtonView}>
<Image
source={menuData[i].icon}
style={[styles.menuIcon]} />
<XXText style={[styles.menuText]}>{menuData[i].text}</XXText>
</View>
</TouchableOpacity>
)
}
return renderOutput;
}
generateMenuData() {
return [
this.generateMenuItem(I18n.t('home'), require('../../images/menu-home.png'), 'HomePage'),
this.generateMenuItem(I18n.t('tutorial'), require('../../images/menu-tutorial.png'), 'SelectTutorialPage'),
this.generateMenuItem(I18n.t('item_list'), require('../../images/menu-add-item.png'), 'itemListPage'),
this.generateMenuItem(I18n.t('report'), require('../../images/menu-report.png'), 'ReportDetailPage'),
this.generateTestPageOnly(I18n.t('hearing_test'), require('../../images/menu-demotest.png')),
this.generateMenuItem(I18n.t('equalizer'), require('../../images/menu-sound.png'), 'SoundPage'),
this.generateMenuItem(I18n.t('support'), require('../../images/menu-support.png'), 'SupportPage'),
this.generateMenuItem(I18n.t('account_settings'), require('../../images/menu-settings.png'), 'SettingsPage'),
{
text: I18n.t('logout'),
icon: require('../../images/menu-logout.png'),
onPressMenu: () => {
Alert.alert(
I18n.t('item'),
I18n.t('logout_confirmation'),
[
{ text: I18n.t('cancel'), onPress: () => { } },
{ text: I18n.t('logout'), onPress: () => { this.onPressLogout() } }
]
)
}
},
];
}
generateTestPageOnly(label, icon) {
let deviceAddress;
let versionData;
// console.log('what is the props here: ', this.props.devices)
function loopThruObject(objectOfDevices) {
for (let [key, value] of Object.entries(objectOfDevices)) {
for (let [newKey, newValue] of Object.entries(value)) {
if (newKey === 'macAddress') {
deviceAddress = newValue;
}
if (newKey === 'version')
versionData = newValue
}
}
return;
}
loopThruObject(this.props.devices)
let currentDevice = this.props.devices[deviceAddress];
let newParams = {
navIndex: 1,
device: currentDevice
};
if (this.props.aDeviceHasBeenConnected === true) {
return {
text: label,
icon: icon,
onPressMenu: () => {
let testPageIdentifier = TestPageFactory.getIdentifier({ isDemo: false, versionData: versionData });
console.log('what is the testPage identifier: ', this.props.navigation)
// NavUtils.push(NavigationActions, testPageIdentifier, { device: currentDevice });
NavigationActions.dispatch({ type: 'Navigation/NAVIGATE', routeName: 'FittingTestPage', params: newParams })
}
}
}
if (this.props.aDeviceHasBeenConnected === false) {
return {
text: 'N/A',
icon: icon,
onPressMenu: () => {
}
}
}
}
generateMenuItem(label, icon, onPressId, onlyNotConnected = false) {
return {
text: label,
icon: icon,
onPressMenu: () => {
this.onPressMenu(onPressId);
},
onlyNotConnected
}
}
renderProfileImage() {
imageSource = this.state.profileImage;
let deviceCount = Object.keys(this.props.devices).length;
let deviceConnectionString = this.generateConnectedString(deviceCount);
return (
<View style={styles.profileImageSubContainer}>
<Image
key={this.state.profileImage.uri}
source={imageSource}
style={styles.profileImage} />
{/* <XXText style={[styles.profileText]} fontWeight='Regular'>{this.state.profileName}</XXText> */}
<XXText style={[styles.deviceText]} fontWeight={'Light'}>{deviceConnectionString}</XXText>
</View>
)
}
generateConnectedString(deviceCount) {
let deviceConnectionString;
if (deviceCount === 1) {
deviceConnectionString = vsprintf(I18n.t('one_item_connected_format_str'), deviceCount.toString());
}
else {
deviceConnectionString = vsprintf(I18n.t('items_connected_format_str'), deviceCount.toString());
}
return deviceConnectionString;
}
renderNavigator() {
return (
<RootNavigator
ref={nav => {
this.navigator = nav;
}}
onNavigationStateChange={(prevState, currentState) => {
this.handleNavSceneChange(prevState, currentState);
}} />
)
}
handleNavSceneChange(prevState, currentState) {
const currentScreen = NavUtils.getCurrentRouteNameFromState(currentState);
this.currentPageId = currentScreen;
this.updateStatusBarStyle(currentScreen);
}
renderXXerlay() {
// Workaround for Android position absolute bug
// issue : https://github.com/facebook/react-native/issues/8923
let visible = this.state.isMenuVisible;
return (
<TouchableWithoutFeedback
onPress={() => { this.hideMenu() }}>
<View style={[styles.XXerlayView, { height: visible ? null : 0 }]} />
</TouchableWithoutFeedback>
)
}
renderSyncXXerlay() {
let visible = this.props.pageStatus.syncModal;
if (!visible) {
return null;
}
return (
<XXSyncXXerlay
visible={visible} />
)
}
//
// EVENTS
//
onPressMenu(pageId) {
requestAnimationFrame(() => {
if (this.currentPageId !== pageId) {
if (pageId === 'DemoTestPage') {
NavUtils.push(this.navigator, pageId);
}
else {
NavUtils.resetTo(this.navigator, pageId);
}
this.updateStatusBarStyle(pageId);
}
this.hideMenu();
});
}
onPressLogout() {
this.setState({ loggingOut: true }, () => {
this.props.logout();
});
}
onUpdateMenu(isMenuVisible) {
if (isMenuVisible === undefined || this.previousMenuVisible === isMenuVisible) {
return;
}
this.previousMenuVisible = isMenuVisible;
// When menu needs to be opened
if (isMenuVisible) {
StatusBarManager.setStyle('light-content', isMenuVisible);
Animated.timing(
this.state.animatedPosition,
{
tXXalue: {
// Animate the RootPage to the right.
// x: 317 / 375 * DEVICEINFO.SCREEN_WIDTH,
// y: 126 / 667 * DEVICEINFO.SCREEN_HEIGHT,
x: 317 / 375 * DEVICEINFO.SCREEN_WIDTH,
y: 1 / 1000 * DEVICEINFO.SCREEN_HEIGHT
},
// duration: 300
// debugging mode
duration: 300
}
).start();
return;
}
// When menu needs to be closed
StatusBarManager.setStyle(this.routeStatusBarStyle, isMenuVisible);
Animated.timing(
this.state.animatedPosition,
{
tXXalue: {
x: 0,
y: 0
},
duration: 300
}
).start();
}
//
// Methods
//
initializeBluetooth() {
// Event listeners for XXBluetoothManager
this.itemDeviceStatusChanged = XXBluetoothEventEmitter.addListener('itemDeviceStatusChanged', event => {
// console.log('ROotpage; . initializeBluetooth: ', event)
this.getConnectedDevice();
});
this.getConnectedDevice();
}
destroyBluetooth() {
if (this.itemDeviceStatusChanged) {
this.itemDeviceStatusChanged.remXXe();
}
}
getConnectedDevice() {
console.log('when does getconnectedDevice get fired inside rootpage? :D', this.props)
XXBluetoothManager.getDeviceAddress((error, address) => {
if (error) {
// Not connected
this.setState({ deviceAddress: null });
return;
}
// console.log('when can we read error, address', error, address)
this.setState({ deviceAddress: address });
});
}
initAndroidHardwareBackEvent() {
BackHandler.addEventListener('hardwareBackPress', () => {
if (!this.navigator) {
return false;
}
if (this.navigator.state.index > 0) {
const backAction = NavigationActions.back({
key: null
});
this.navigator.dispatch(backAction);
return true;
}
return false;
});
}
toggleMenu() {
this.props.toggleRootMenu();
}
hideMenu() {
this.props.closeRootMenu();
}
}
const mapStateToProps = (state) => {
return {
signinStatus: state.page.signin,
pageStatus: state.page.root,
profile: state.user.profile,
devices: state.device.devices,
aDeviceHasBeenConnected: state.device.aDeviceHasBeenConnected,
}
}
const mapDispatchToProps = (dispatch) => {
return {
logout: () => {
dispatch(logout());
},
closeSyncModal: () => {
dispatch(closeSyncModal());
},
toggleRootMenu: () => {
dispatch(toggleRootMenu());
},
openRootMenu: () => {
dispatch(openRootMenu());
},
closeRootMenu: () => {
dispatch(closeRootMenu());
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(RootPage);
const styles = StyleSheet.create({
rootView: {
flex: 1,
flexDirection: 'column',
},
menuView: {
width: DEVICEINFO.SCREEN_WIDTH,
height: '100%',
backgroundColor: '#aab942',
position: 'absolute',
top: 0,
left: 0
},
menuTopArea: {
backgroundColor: '#b7c846',
// height: DEVICEINFO.SCREEN_HEIGHT * 0.35,
height: DEVICEINFO.SCREEN_HEIGHT * 0.28,
width: '100%',
flexDirection: 'column',
},
menuBottomArea: {
// backgroundColor: '#aab942',
width: '100%',
flex: 1,
flexDirection: 'column',
},
animatedView: {
position: 'relative',
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff'
},
profileImageSubContainer: {
height: '100%',
flexDirection: 'column',
justifyContent: 'center',
marginLeft: 40,
marginTop: 10,
},
profileImage: {
width: DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 70 : 80,
height: DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 70 : 80,
borderRadius: (DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 70 : 80) / 2
},
profileText: {
marginTop: DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 6 : 16,
fontSize: Math.min(LAYOUTINFO.DESIGN_WIDTH_RATIO * 20, 21),
color: '#fff',
backgroundColor: 'transparent'
},
deviceText: {
marginTop: DEVICEINFO.IS_EXTRA_SMALL_SCREEN ? 6 : 16,
fontSize: Math.min(LAYOUTINFO.DESIGN_WIDTH_RATIO * 14, 15),
color: '#fff',
backgroundColor: 'transparent'
},
menuContainer: {
flex: 1,
width: '100%',
height: '100%',
flexDirection: 'column',
alignItems: 'flex-start',
},
// menubutton
menuButtonTouch: {
marginTop: 25 * HEIGHT_RATIO,
marginLeft: 38,
},
menuButtonView: {
flexDirection: 'row',
alignItems: 'center'
},
menuIcon: {
width: 18,
height: 18,
resizeMode: 'contain'
},
// menubuttontext
menuText: {
marginLeft: 17,
fontSize: Math.min(LAYOUTINFO.DESIGN_WIDTH_RATIO * 22, 22),
color: '#fff'
},
XXerlayView: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: null,
height: null,
backgroundColor: 'rgba( 255, 255, 255, 0.0001 )',
zIndex: 9999
}
});
Here is the RootNavigator page
import { Platform } from 'react-native';
import { StackNavigator } from 'react-navigation';
// Pages
import EntryPage from '../EntryPage/EntryPage';
import SigninPage from '../SigninPage/SigninPage';
import SignupPage from '../SignupPage/SignupPage';
import ReportSinglePage from '../ReportSinglePage/ReportSinglePage';
import HomePage from '../HomePage/HomePage';
import VolumePage from '../VolumePage/VolumePage';
import ReportPage from '../ReportPage/ReportPage';
import ReportDetailPage from '../ReportDetailPage/ReportDetailPage';
import ItemListPage from '../ItemListPage/ItemListPage';
import AddItemPage from '../AddItemPage/AddItemPage';
import SupportPage from '../SupportPage/SupportPage';
import SettingsPage from '../SettingsPage/SettingsPage';
import SoundPage from '../SoundPage/SoundPage';
import VerticalSoundPage from '../SoundPage/VerticalSoundPage';
import SoundSyncPage from '../SoundSyncPage/SoundSyncPage';
import TestPage from '../TestPage/TestPage';
import iOSTestPage from '../TestPage/iOSTestPage';
import FittingTestPage from '../TestPage/FittingTestPage';
import SendResultSlide from '../SoundSyncPage/SendResultSlide';
import ModePage from '../ModePage/ModePage';
import PolicyPage from '../PolicyPage/PolicyPage';
import PrivacyPage from '../PrivacyPage/PrivacyPage';
import TermsAndConditionsPage from '../TermsAndConditionsPage/TermsAndConditionsPage';
import SelectTutorialPage from '../TutorialPage/SelectTutorialPage';
import PreparingTutorial from '../TutorialPage/PreparingTutorial';
import AddItemTutorial from '../TutorialPage/AddItemTutorial';
import HearingTestTutorial from '../TutorialPage/HearingTestTutorial';
import ReportTutorial from '../TutorialPage/ReportTutorial';
import EqualizerTutorial from '../TutorialPage/EqualizerTutorial';
import AddItemTutorialCompletion from '../TutorialPage/AddItemTutorialCompletion';
import AddItemTutorialFailure from '../TutorialPage/AddItemTutorialFailure';
import HearingTestTutorialCompletion from '../TutorialPage/HearingTestTutorialCompletion';
// import RootPage from '../RootPage/RootPage';
const RootNavigator = StackNavigator(
{
// RootPage: {screen: RootPage},
EntryPage: { screen: EntryPage },
SignupPage: { screen: SignupPage },
HomePage: { screen: HomePage },
TestPage: { screen: TestPage },
iOSTestPage: { screen: iOSTestPage },
FittingTestPage: { screen: FittingTestPage },
ReportPage: { screen: ReportPage },
ReportDetailPage: { screen: ReportDetailPage },
ReportSinglePage: { screen: ReportSinglePage },
ItemListPage: { screen: ItemListPage },
AddItemPage: { screen: AddItemPage },
SoundPage: { screen: VerticalSoundPage },
SoundSyncPage: { screen: SoundSyncPage },
SupportPage: { screen: SupportPage },
SettingsPage: { screen: SettingsPage },
VolumePage: { screen: VolumePage },
SendResultSlide: { screen: SendResultSlide },
PolicyPage: { screen: PolicyPage },
PrivacyPage: { screen: PrivacyPage },
TermsAndConditionsPage: { screen: TermsAndConditionsPage },
SelectTutorialPage: { screen: SelectTutorialPage },
PreparingTutorial: { screen: PreparingTutorial },
AddItemTutorial: { screen: AddItemTutorial },
HearingTestTutorial: { screen: HearingTestTutorial },
ReportTutorial: { screen: ReportTutorial },
EqualizerTutorial: { screen: EqualizerTutorial },
AddItemTutorialCompletion: { screen: AddItemTutorialCompletion },
AddItemTutorialFailure: { screen: AddItemTutorialFailure },
HearingTestTutorialCompletion: { screen: HearingTestTutorialCompletion },
},
{
initialRouteName: 'EntryPage',
navigationOptions: {
header: null,
}
},
{
headerMode: 'none'
}
);
export default RootNavigator;
And just in case, here is the helper function this I'm trying to use:
static push(navigator, pageId, params = {}) {
let newParams = { navIndex: 1 };
if (navigator.state.index) {
newParams.navIndex = navigator.state.index + 1;
}
else if (navigator.state.params && navigator.state.params.navIndex) {
newParams.navIndex = navigator.state.params.navIndex + 1;
}
Object.assign(newParams, params);
navigator.dispatch({ type: 'Navigation/NAVIGATE', routeName: pageId, params: newParams });
}