Why is the parameter inside the action coming back as undefined when using redux? - react-native

Currently, I have a page which renders a list of dates, When a user presses a certain date, the user is then taken to a new page which renders the graph of the date that they pressed.
I want to use redux to update props, so that I can render a specific graph based on which button a user has pressed.
Inside my renderList() I return a mapped array that in turn returns a bunch of TouchableOpacities. Inside each TouchableOpacity, inside the onPress event, another function is called that passes all of the information about the test as a parameter. renderList looks like this.
let sorted = _.orderBy(this.props.testResults, testResult => testResult.created, 'desc');
moment.locale(localeToMomentLocale(I18n.locale));
return sorted.map((result, index) => {
let formattedDate = moment(result.created).format(I18n.t('report_header_dformat'));
let correctedDate = vsprintf(I18n.t('report_date_correction'), [formattedDate]);
let analysis = TestAnalysis.run(result);
return (
<TouchableOpacity
onPress={() => this.resultOrTest(result)}
style={styles.row} key={'_' + index}>
</TouchableOpacity>
resultOrTest looks like this:
resultOrTest = (result) => {
console.log('ReportDetailPage: resultOrTest: showing result: ', result.id);
this.props.setResultIdToProps(result.id);
this.props.navigation.navigate('ReportSinglePage');
};
mapDispatchToProps looks like this:
const mapDispatchToProps = (dispatch) => {
return {
setResultIdToProps: () => {
dispatch(setResultIdToProps());
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ReportDetailPage);
inside my actions/user.js page.
export const setResultIdToProps = (resultId) => {
// var newId = resultId.toString();
console.log('actions/user.js setResultIdToProps: resultid.......', resultId);
return (dispatch, getState) => {
dispatch({
type: SET_RESULT_ID_TO_PROPS,
resultId
});
}
};
Why does resultId keep coming back as undefined? Did I pass the wrong value/Parameter?

You need to properly pass the parameter to your action dispatcher in mapDispatchToProps. Right now, you're not passing the resultId, hence it is passed as undefined.
const mapDispatchToProps = (dispatch) => {
return {
setResultIdToProps: (resultId) => {
dispatch(setResultIdToProps(resultId));
}
}
}

Related

React native with axios fetch first post delayed

I am using react native, and axios.
I have two parts.
The exercice list that is rendered with useEffect in a div. Inside, there is a Input form which once pressed the Add set button, the set is added to the database and the exercices are fetched again with the passed function.
The main problem is that when I first add an exercice, the exercice s not rendering. I must go back and come again in the page to render the first one. after doing this process I can add as many exercices... And with delete is same. I can delete any exercice but when deleting the last one, it persist and I must leave the page to see the changes...
THIS IS THE FUNCTION THAT ADD THE exercices. It executes once the alert button is pressed
const NewExercice = ({dayID, getAllEx}) => {
// States and ontext change functions
const [exName, setexName] = useState('');
const [comment, setcomment] = useState('');
const handleExname = text => setexName(text);
const handleComments = text => setcomment(text);
// Add new exercices
const handleNewExercice = async () => {
try
{
const status = await data.post('/api/create-exercice', {dayID, exName, comments: comment});
Alert.alert(
'Exercice created',
'Please add new sets to existing exercices',
[
{
text: 'Ok!',
// Fetch again for all the exercices
onPress: getAllEx
}
]
)
}
catch (error)
{
console.log(error);
}
}
Bellow is the component that adds map over the array state
<View>
{error ? (<Text>No exercices created yet.</Text>) :
exArray.map(obj => (
<ExerciceWrapper getAllEx={getAllExercices} navigation={navigation} key={obj.exID} object={obj} />
))}
</View>
Bellow is the function that fetch the data from the DB and set the state to be able to be rendered in the component above
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}
useEffect(() => {
getAllExercices();
}, []);
You need to toggle the error value when you have successful fetch as well
update code to this
const getAllExercices = async () => {
try
{
const response = await data.get('/api/get-all-ex/' + dayID);
setExArray(response.data);
setError(response.data.length < 1)
}
catch (error)
{
if (error.response.status === 404) return setError(true);
else return console.log(error);
}
}

Why this navigation.goBack is not working as I intended ? How can I get the Last Active State?

I got 3 pages
homepage, productList and productDetails
When going from homepage to productList I pass a route param,
navigation.navigate('productList', { showCategory: 'productListA'} )
InitialProcess when component mounted
Inside the productList page when the component is mounted. I am declaring use state like this.
const {showCateory} = route.params;
const [activeTab, setActiveTab] = useState(showCateory);
and calling api using that activeTab
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
User Interaction
But I also add the button in the productList so that user can change the current active tab
<TouchableOpacity onPress={() => changeTab()}></TouchableOpacity>
const changeTab = async () => {
await setActiveTab('productListB'),
await dispatch(fetchProductList(activeTab)
}
Take note that right now active tab and data coming from api is different from when the component is start mounted.
Navigation Change again
When use goes from productList to productDetails. All thing is fine.
But inside the product details I am going back to productList with this.
navigation.goBack().
When I am back in productList page The activeTab is change back to productListA and the data is change back to when component is mounted
Can I pass or change the route params when calling navigation.goBack()?
add activeTab in useEffect depedineces.
as docs say
The array of dependencies is not passed as arguments to the effect function. Conceptually, though, that’s what they represent: every value referenced inside the effect function should also appear in the dependencies array. In the future, a sufficiently advanced compiler could create this array automatically.
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
//this value will always updated when activeTab change
activeTab,
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, [activeTab]); //<<<<< here
also you need to know setState() does not always immediately update the component. see here
so change this
const changeTab = async () => {
//await setActiveTab('productListB'),
//await dispatch(fetchProductList(activeTab)
setActiveTab('productListB')
dispatch(fetchProductList('productListB'))
}
This might be happening because route.params is still set to { showCategory: 'productListA'} when you are coming back to the screen.
If this is the case, you can fix it by Changing params object in changeTab() like
navigation.setParams({
showCategory: 'productListB',
});
I hope this will fix your problem.
This happens because the callback function inside the focus listener uses the initial value of the state when the function was defined (at initial page render) . Throughout the lifespan of listener the callback function uses this stale state value.You can read more about this behaviour in this answer
Although the answer by Ahmed Gaber works in this case as the listener is cleared and redefined after each state change.Another common work-around is to use an useRef instead of useEffect.A ref is basically a recipe that provides a mutable object that can be passed by reference.
In your case you can initialise activeTab with navigation param value using useRef hook as :
const activeTab = useRef(showCateory);
and the focus listener callback function should be changed to use the Reference current value as
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
async function fetchData() {
try {
await dispatch(
fetchProductList(
activeTab.current, //<<<<<<---------here
),
);
} catch (err) {
console.log(err);
}
}
fetchData();
});
return unsubscribe;
}, []);
and the changeTab function can directly update reference current value
const changeTab = async () => {
setActiveTab.current = 'productListB';
dispatch(fetchProductList('productListB'))
}

how to handle the return values from reducer, inside a functional component

This may be a basic question, but I'm new to React Native and stuck here.
My code pasted below. reducer and functional component. I want to capture the response returned from reducer.
reducer.js
export const ActivationCenterReducer = (
state = INIT_KIT_STATE,
{ type, payload = {} }
) => {
switch (type) {
case 'KIT_ACTIVATION_SUCCESS_DATA': {
const { message, response_code, apiLoading, apiError } = payload;
return {
...state,
apiLoading: apiLoading,
apiError: apiError,
message: message,
response_code: response_code
};
}
// ...
}
// ...
};
Functional Component class:
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
const handleKitActivation = () => {
/*This will call the validation() inside action.js and that follows the reducer.js file. where reducer.js file returning the values on success response. but I am not able to access that response_code returned from reducer.
How to save the response_code from the below dispatch function.*/
dispatch(Validation(locator, pin));
if (response_code === 200) {
// should navigate to the next screen
}
};
};
My question is how to capture the returned response_code from reducer.
I'm able to navigate to the next screen on clicking the submit button couple of times.I notice that first time when the dispatch function is called, the state of the response_code is not updating , hence the response_code != 200.
I want a way to capture the response and assign to variable.
Thanks in advance.
You are probably looking at the old value of response_code in your handleKitActivation.
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
const handleKitActivation = () => {
dispatch(Validation(locator, pin));
// HERE the response_code does not have result value
// of your calling dispatch(Validation(locator, pin)) above yet
if (response_code === 200) {
// should navigate to the next screen
}
};
};
I suggest to move your response_code hanfling to the useEffect:
const kitActivationCenter = ({ route, navigation }) => {
const response_code = useSelector(
store => store.kitActivationCenter.response_code
);
// this effect will run whenever your response_code changes
useEffect(() => {
if (response_code === 200) {
// should navigate to the next screen
}
}, [response_code]);
const handleKitActivation = () => {
dispatch(Validation(locator, pin));
};
};

React Native hooks - correct use of useEffect()?

I'm new to hooks and ran across this setup on SO and wanted to confirm that this is the correct pattern. I was getting the RN "unmounted component" leak warning message before and this seemed to solve it. I'm trying to mimic in some way compnentDidMount. This is part of a phone number verify sign up flow and onMount I want to just check for navigation and then fire off a side effect, set mounted true and then unmount correctly.
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const [didMount, setDidMount] = useState(false)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
setDidMount(true)
}
return () => setDidMount(false)
}, [])
if (!didMount) { return null }
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}
RN 0.62.2 with react-nav 5 - thanks!
Since signInWithPhoneNumber is a async function and will setState you will see warning it the component is unmounted before the response is available
In order to handle such scenarios you can keep a variable to keep track whether its mounted or not and then only set state is the mounted variable is true
However you do not need to return null if component has unmounted since that doesn't accomplish anything. The component is removed from view and will anyways not render anything.
Also you do not need to maintain this value in state, instead use a ref
const SMSVerifyEnterPinScreen = ({ route, navigation }) => {
const isMounted = useRef(true)
const { phoneNumber } = route.params
useEffect(() => {
if(navigation) {
signInWithPhoneNumber(phoneNumber)
}
return () => {isMounted.current = false;}
}, [])
async function signInWithPhoneNumber(phoneNumber) {
const confirmation = await auth().signInWithPhoneNumber('+1'+phoneNumber)
...
}
return (
...
)
}

Make two actions work simultaneously - react native and redux

I've a button that sends two actions. First one adds the user infos in an array if certain condition is met and 2nd one sends the data to the server.
Since both actions are in onPress function, the 2nd action doesn't wait till it adds up the infos in an array. Henceforth, it always sends empty array.
How can I make this two actions work simultaneously.
<TouchableOpacity
onPress={() => {
if (true) {
this.props.AuthUserInfoGet(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo); //calculates & return SignUpUsers
}
this.props.SignUpCheck(SignUpUsers); //upload SignUpUsers but SignUpCheck is always empty here
}}
>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const {SignUpUsers} = state.Auth;
//it gives an empty array first and then expected value
console.log('SignUpUsersz', SignUpUsers);
return {SignUpUsers};
};
Action:
export const AuthUserInfoGet = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return ({
type: SIGN_UP_USER_INFO_GET,
payloadName: SignUpName,
payloadDesignation: SignUpDesignation,
payloadEmail: SignUpEmail,
payloadMobile: SignUpMobileNo,
});
}
export const SignUpCheck = (userInfo) => {
console.log('userInfo', userInfo); // userInfo is always empty
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_USER_INFO_GET:
return { ...state, SignUpUsers: [...state.SignUpUsers, {member_name: actions.payloadName, designation: actions.payloadDesignation,
email: actions.payloadEmail, mobile_number: actions.payloadMobile}] };
Given your current Redux-structure, I think what makes the most sense to use the componentDidUpdate life-cycle method.
The main reason is because your component ultimately needs to get updated data from Redux via props and needs to re-render. When you execute the first action, that user-data coming from the API is not immediately available in the current call-stack, so you'll always be passing an empty array (given your initial value of SignUpUsers: [])
Note that most React-Redux flows follow this path:
User-Event -> Action-Creator -> API (Data) -> Redux -> Component
Your click-event is at step 1 and triggers this action: this.props.AuthUserInfoGet(...args)
But React/Redux needs to go through that entire flow before you can use the new data.
This is where the componentDidUpdate() event comes in-handy because you can write logic when the component is re-rendered by new props or state.
Something like this would totally work:
componentDidUpdate(prevProps){
if(prevProps.SignUpUsers.length !== this.props.SignUpUsers.length){
//execute action
this.props.SignUpCheck(this.props.SignUpUsers)
}
}
For that I would suggest you take a look at redux-thunk middleware.
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
And based on your example, the code will end up like this:
<TouchableOpacity
onPress={() => this.props.uploadSignUpUsers(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo)}>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const { Auth: { SignUpUsers } } = state;
return { SignUpUsers };
}
Actions:
export const SIGN_UP_GET_USER_INFO_SUCCESS = "SIGN_UP_GET_USER_INFO_SUCCESS";
export const SIGN_UP_UPLOAD_SUCCESS = "SIGN_UP_UPLOAD_SUCCESS";
export const uploadSignUpUsers = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return async (dispatch, getState) => {
// here you can make the api call or any other async calculations
const { data: AuthUserInfo, error } = await api.post(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo);
dispatch({
type: SIGN_UP_GET_USER_INFO_SUCCESS,
payloadName: AuthUserInfo.SignUpName,
payloadDesignation: AuthUserInfo.SignUpDesignation,
payloadEmail: AuthUserInfo.SignUpEmail,
payloadMobile: AuthUserInfo.SignUpMobileNo,
});
const { Auth: { SignUpUsers } } = getState()
// and now you can upload your SignUpUsers
const { data: uploadData, error } = await.api.post(SignUpUsers)
dispatch({
type: SIGN_UP_UPLOAD_SUCCESS,
...uploadData // spread upload data to make it available in reducers
});
}
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_GET_USER_INFO_SUCCESS: {
const { payloadName, payloadDesignation, payloadEmail, payloadMobile } = actions
return {
...state,
SignUpUsers: [ ...state.SignUpUsers, {
member_name: payloadName,
designation: payloadDesignation,
email: payloadEmail,
mobile_number: payloadMobile
}]
}
}