Im using redux and i want to render my main drawer based if an user is logged on or not. I have this 'init' class that will check for that and do it accordingly.
I'll post the code and explain it at the end:
Init.js
const mapStateToProps = (userReducer) => {
return {
...userReducer,
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatch,
getCurrentUser: () => {
dispatch(getCurrentUser())
}
}
}
class Init extends Component {
constructor(props) {
super(props);
this.props.getCurrentUser()
console.log("TEST>>")
console.log(this.props)
}
chooseInitialRoute() {
const { navigation, user } = this.props;
if (user) {
navigation.dispatch(StackActions.reset(
{ index: 0, key: null, actions: [NavigationActions.navigate({ routeName: 'LoggedDrawer' })] }
))
} else {
navigation.dispatch(StackActions.reset(
{ index: 0, key: null, actions: [NavigationActions.navigate({ routeName: 'UnloggedDrawer' })] }
))
}
}
user_actions.js
export function getCurrentUser() {
return (dispatch) => {
Parse.User.currentAsync().then(function (user) {
if (user) {
dispatch({ type: 'GET_USER', payload: { user } })
} else {
dispatch({ type: 'GET_USER_REJECTED', payload: error })
}
})
}
}
The console.log(this.props) returns undefined if called inside the constructor (same happens when I do console.log(props). But if I call it inside render() I get the props correcly (meaning that I get the correct user object from parse server).
Is there any way to update the props before the render() method? I want to get the 'updated' props on the chooseInitialRoute()
Thanks in advance. If more code is needed let me know.
If console.log(props) is undefined in the constructor this means the component later gets the props via componentWillReceiveProps and i dont know where you call InitialRoute but you might consider putting it in the lifecycle function instead:
componentWillReceiveProps(props) {
const { navigation, user } = props;
if (user) {
navigation.dispatch(StackActions.reset(
{ index: 0, key: null, actions: [NavigationActions.navigate({ routeName: 'LoggedDrawer' })] }
))
} else {
navigation.dispatch(StackActions.reset(
{ index: 0, key: null, actions: [NavigationActions.navigate({ routeName: 'UnloggedDrawer' })] }
))
}
}
Note that if your props are undefined in the constructor then your component may render multiple times before your component finally receives props via componentWillReceiveProps.
Related
I currently started learning redux. My code was working perfectly with core redux, then I tried out #reduxjs/toolkit and now I'm unable to access the function to change the state in the store. Here is my code of reducer.
const seasonEdits = createSlice({
name: "seasons",
initialState: [],
reducers: {
addSeason(state, action) {
state.push(action.payload);
console.log("this here");
},
removeSeason(state, action) {
state.filter((season) => season.id !== action.payload);
},
markComplete(state, action) {
state.map((season) => {
if (season.id == action.payload) season.isWatched = !season.isWatched;
});
},
},
});
export const { addSeason, removeSeason, markComplete } = seasonEdits.actions;
export default seasonEdits.reducer;
and my store.js file
import { configureStore } from "#reduxjs/toolkit";
import seasonReducer from "./reducer";
export default store = configureStore({
reducer: {
seasons: seasonReducer,
},
});
and the add.js file which has add functionality. Calling a handleSubmit function which is creating an object and adding it to an array which is the state in store.
const handleSubmit = async () => {
try {
if (!name || !totalNoSeason) {
return alert("Please add both fields");
}
const seasonToAdd = {
id: shortid.generate(),
name,
totalNoSeason,
isWatched: false,
};
addSeason(seasonToAdd);
navigation.navigate("Home");
} catch (error) {
console.log(error);
}
};
const mapDispatchToProps = (dispatch) => {
return {
addSeason: (data) => dispatch(addSeason(data)),
};
};
Add.propTypes = {
addSeason: propTypes.func.isRequired,
};
export default connect(null, mapDispatchToProps)(Add);
The issue is that array.map() and array.filter() return new arrays! Right now your reducers are calling those functions, and then just throwing away the new arrays:
removeSeason(state, action) {
// The return value is thrown away and ignored!
state.filter((season) => season.id !== action.payload);
},
You need to return the new value:
removeSeason(state, action) {
// Now RTK will see the new return value
return state.filter((season) => season.id !== action.payload);
},
See https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state for more details.
I'm building a mobile app with react-native, redux, and react-navigation.
I have been thinking which code should have a function that is about screen transition(ex. this.props.navigation.navigate('NextScreen')).
For example, in my app, sign in process is below.
Sign In Process
As you see, the app runs this.props.navigation.navigate() in handleSignIn function in SignIn screen.
[Questions]
Sometimes the uid value in state is blank, it should be filled if user sign in successfully, but sometimes not. How do I solve it?
this.props.navigator.navigator() is executed in the function that is defined in the screen component, is it good?
There are my code.
SignIn Screen
import React, { Component } from 'react';
import { ActivityIndicator, Keyboard, KeyboardAvoidingView, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
:
class SignIn extends Component {
async handleSignIn() {
const { navigation, requestSignIn } = this.props;
const { uid, email, password, error } = this.props.auth;
Keyboard.dismiss();
requestSignIn(email, password);
// sometimes this uid is blank
if (uid) {
alert('success');
// this function should be executed here?
navigation.navigate('Match', { uid: uid });
} else {
alert(error);
}
}
render() {
const { navigation, changeText } = this.props;
const { email, password, loading } = this.props.auth;
return (
:
<Button gradient onPress={() => this.handleSignIn()}>
{ loading ?
<ActivityIndicator size='small' color='white' /> :
<Text bold white center>Sign In</Text>
}
</Button>
:
)
}
}
const mapStateToProps = state => {
return {
auth: state.auth
}
};
const mapDispatchToProps = dispatch => {
return {
requestSignIn: (email, password) => dispatch(auth.requestSignIn(email, password)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
:
Action
:
export const REQUEST_SIGN_IN_SUCCESS = 'REQUEST_SIGN_IN_SUCCESS';
export const REQUEST_SIGN_IN_FAILURE = 'REQUEST_SIGN_IN_FAILURE';
export function requestSignIn(email, password) {
return async function (dispatch) {
// change loading status
dispatch(startedRequest());
if (email && password) {
await firebase.auth().signInWithEmailAndPassword(email, password)
.then(response => {
if (response) {
// save email and password in local secure storage.
SecureStorage.setItem('email', email);
SecureStorage.setItem('password', password);
dispatch(requestSignInSuccess(response.user.uid))
} else {
return Promise.resolve(new Error(response));
}
})
.catch(error => {
switch (error.code) {
case 'auth/user-not-found':
dispatch(requestSignInFailure('user not found'));
break;
case 'auth/invalid-email':
dispatch(requestSignInFailure('invalid email'));
break;
default:
dispatch(requestSignInFailure('something went wrong'))
}
})
} else {
dispatch(requestSignInFailure('error message from else statement'))
}
}
}
export function requestSignInSuccess(uid) {
return {
type: REQUEST_SIGN_IN_SUCCESS,
payload: {
uid: uid
}
}
}
export function requestSignInFailure(errorMessage) {
return {
type: REQUEST_SIGN_IN_FAILURE,
payload: {
errorMessage: errorMessage
}
}
}
Reducer
import * as ActionType from '../actions/auth';
const initialState = {
uid: '',
email: '',
password: '',
isLoading: false,
error: {
message: ''
}
};
const auth = (state=initialState, action) => {
const { type, payload } = action;
switch (type) {
case ActionType.STARTED_REQUEST:
return Object.assign({}, state, {
isLoading: true
});
case ActionType.CHANGE_TEXT:
return Object.assign({}, state, {
[payload.key]: payload.value
});
case ActionType.REQUEST_SIGN_IN_SUCCESS:
return Object.assign({}, state, {
uid: payload.uid,
isLoading: false,
});
case ActionType.REQUEST_SIGN_IN_FAILURE:
return Object.assign({}, state, {
isLoading: false,
error: {
message: payload.errorMessage,
},
});
default:
return { ...state };
}
};
export default auth;
First of all, yes you should navigate into the component after your sign-in business logic works.
About the second question, it is wrong with using "requestSignIn" method. You need to send a callback from requestSignIn method and it should something like this:
requestSignIn((result) => {
if(result){
const { uid } = result;
uid && navigate("Match", {uid});
}
})
As I can see in your action, you already send a dispatch, therefore, it should work as the above example.
Why it is not working with your logic?
It is so simple because it is not working sync, it just goes to the next if check so it does not wait until the requestSignIn method is finished. You can even use async/await but dispatch (Promise) will solve it for you :)
One last thing, I suggest you to use React Navigation Helpers for handling all navigation logics. I've written it and it solves so many dirty logic for you :)
About the second question, I follow this link.
https://github.com/daose/react-native-template/issues/1
Finally, I use NavigationActions in the action instead of this.props.navigation.navigate()
I have a very simple component which set state values using axios. However, the state value is being changed in the render method.
constructor(props) {
super(props);
const { navigation } = this.props;
const approveID = navigation.getParam('approveID', '0');
this.state = {
selectedApprove: approveID,
reason: '',
};
}
componentDidMount() {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.getReviewApp(tenantID);
}
getReviewApp(tID) {
axios.get('http://myserver/getData', {
params: {
method: 'getApplicantReview',
tenantID: tID
}
}).then((response) => {
const result = response.data.DATA[0];
this.setState({
selectedApprove: result[0],
reason: result[1]
});
}).catch((error) => {
// handle error
console.log(error);
});
}
...
render() {
console.log(this.state);
...
}
When I run the app the console shows 2 times. First is perfect:
Object {
"reason": "Test",
"selectedApprove": "Yes",
}
The second log is with null values and it messes up my component:
Object {
"reason": null,
"selectedApprove": null,
}
Why is it happening?
Thanks
The response object must not look like what you think it looks like.
My application to use to react-navigation the router, in accordance with the API method, the following problems:
react-navigation : why 'routes' of undefined ? https://reactnavigation.org/docs/routers
const SimpleApp = TabNavigator({})
const Simple = StackNavigator({
Home: { screen: SimpleApp },
Login: { screen: Login },
})
const defaultGetStateForAction = Simple.router.getStateForAction;
Simple.router.getStateForAction = (action, state) => {
console.log('action ===',action);
console.log('state ===',state);
console.log('......getStateForAction........');
if (true) {
const routes = [
...state.routes,
{key: 'A', routeName: 'Login', params: { name: action.name1 }},
];
return {
...state,
routes,
index: routes.length - 1,
};
}
return defaultGetStateForAction(action, state);
};
export default class App extends Component {
render() {
return (
<Simple />
)
}
}
TypeError:Cannot read property 'routes' of undefined
We are basically overriding the getStateForAction function.
This function is present in the StackRouter file. What it does is check if the initial state is undefined. If it is, it creates it.
StackRouter.js
getStateForAction(
action: NavigationAction,
state: ?NavigationState
): ?NavigationState {
// Set up the initial state if needed
if (!state) {
let route = {};
... // The code present in the file.
}
To learn more about this look into StackRouter.js.
As the state itself is undefined because initial state is not set, ...state.routes in the code is producing the error.
Currently this will work:
Simple.router.getStateForAction = (action, state) => {
console.log('action ===',action);
console.log('state ===',state);
console.log('......getStateForAction........');
// change the condition to check if state exists.
if (state) {
const routes = [
...state.routes,
{key: 'A', routeName: 'Login', params: { name: action.name1 }},
];
return {
...state,
routes,
index: routes.length - 1,
};
}
return defaultGetStateForAction(action, state);
};
I'm a beginner for react-native and I need to alert to the user based on a status which will be retrieved from an API in every 15 seconds. For this I'm using react-native-background-timer in my main component to call the service. But when app is in some other screen (component) even though the service executes perfectly in the main component, it doesn't update it's props or status depending on the result it received (I guess this should be because I'm in a some other screen and props of main component will not be updated). Due to that alert will not be triggered if app is not in the main component
Can anyone please suggest me an approach for this?
class Home extends Component{
constructor(props){
super(props)
this._onPopUpShowed = this._onPopUpShowed.bind(this)
}
componentDidMount(){
//Initial call after the launch
this.props.fetchLiveOrderData()
//Start timer for polling
const intervalId = BackgroundTimer.setInterval(() => {
isBackgroudLoad=true
this.props.fetchLiveOrderData()
}, 1000*15);
}
render(){
const{payload,isFetching,isError,isSuccess} = this.props.liveOrderData
return(
//Render UI depending on the data fetched
);
}
}
//map state to props
const mapStateToProps = state => {
return {
liveOrderData: state.liveOrderData
}
}
//map dispatch to props
const mapDispatchToProps = dispatch => {
return {
fetchLiveOrderData : () => dispatch(fetchLiveOrderData())
}
}
export default connect(mapStateToProps, mapDispatchToProps) (Home)
liveOrderReducer.js
import {
FETCHING_LIVE_ORDER_DATA, FETCHING_LIVE_ORDER_DATA_SUCCESS, FETCHING_LIVE_ORDER_DATA_ERROR
} from '../constants'
const initialState = {
payload: [],
msg:[],
isFetching: true,
isError: false,
isSuccess: false
}
export default liveOrderReducer = (state = initialState, action) => {
switch(action.type){
case FETCHING_LIVE_ORDER_DATA :
return {
...state,
payload: [],
msg:[],
isFetching: true,
isError: false,
isSuccess: false
}
case FETCHING_LIVE_ORDER_DATA_SUCCESS :
return {
...state,
payload: action.data,
msg:[],
isFetching: false,
isError: false,
isSuccess:true
}
case FETCHING_LIVE_ORDER_DATA_ERROR :
return {
...state,
payload: [],
msg:action.msg,
isFetching: false,
isError: true,
isSuccess:false
}
default:
return state
}
}
index.js
import {
FETCHING_LIVE_ORDER_DATA, FETCHING_LIVE_ORDER_DATA_SUCCESS, FETCHING_LIVE_ORDER_DATA_ERROR
} from '../constants'
import api from '../lib/api'
export const getLiveOrderData = () => {
return {
type : FETCHING_LIVE_ORDER_DATA
}
}
export const getLiveOrderDataSuccess = data => {
return {
type : FETCHING_LIVE_ORDER_DATA_SUCCESS,
data
}
}
export const getLiveOrderDataFailure = () => {
return {
type : FETCHING_LIVE_ORDER_DATA_ERROR
}
}
export const fetchLiveOrderData = () => {
return(dispatch) => {
dispatch(getLiveOrderData())
api.getOrder().then(resp => {
dispatch(getLiveOrderDataSuccess(resp))
}).catch((err) => {
dispatch(getLiveOrderDataFailure(err))
})
}
}
Move the notification code to the container or the root component. This will ensure you will receive notifications even if the user moved away from the home screen.