React-Redux Dispatch Action in Component - react-native

I am fairly new to react native, the issue I'm having is trying to get data from an action in componentdidmount but when I set my props the data is null. The props are being set if I access them in the render method. Can someone please look at the code and tell me what I am doing wrong.
Below is where I'm getting the action
export const Accountability = connect(
// inject states
(state: States) => ({
// props.loading -> modules.app.loading
loading: state.app.loading,
doctorName: state.doctor.doctorName
}),
// inject actions
dispatch => ({
doDoctors: () =>
dispatch(actions.doctor.getDoctors())
})
)(AccountabilityView)
This is where I'm calling it.
render() {
const {loading, doDoctors, doctorName } = this.props
// Getting the data here.
doDoctors()
}
One thing I notice is that I am getting a warning in the console
ExceptionsManager.js:82 Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
UPDATE:
I currently have all my files separate(action, reducer, constants, index). My action gets data from an API call. Below is my reducer:
import { handleActions } from 'redux-actions'
import { LOAD_DOCTORS } from './constants'
export type DoctorState = {
doctorName: string
}
const initialState: DoctorState = {
doctorName: '',
}
export default handleActions(
{
[LOAD_DOCTORS]: (state: DoctorState = initialState, action): DoctorState
=> {
const p = action.payload
return {
doctorName: p.doctorName,
}
},
},
initialState
)
UPDATE:2
This is what the code shows in the console, note doDoctors which returns an array is empty on the first call. When called in ComponentDidMount it only shows the first and not the second.
ComponentDidMount
{screenProps: undefined, navigation: {…}, loading: true, doctorName: "",
doDoctors: ƒ}
render
{screenProps: undefined, navigation: {…}, loading: true, doctorName: "",
doDoctors: ƒ}
{screenProps: undefined, navigation: {…}, loading: true,
doctorName: Array(10), doDoctors: ƒ}
Any help would be appreciated.

Possible events where you could call your action are => componentDidMount and componentWillReceiveProps ... render method is only used for returning some jsx based on the update of your component props:
class YourComponent extends React.Component {
componentDidMount() {
// Here's where you call your action,
// first time your component is loaded: <<<===
this.props.doDoctors();
}
componentWillReceiveProps(nextProps) {
// Here's where you could call your action,
// if the component is already mounted: <<<===
this.props.doDoctors();
}
render() {
const {loading, doctorName } = this.props;
return (
<View>
...
</View>
);
}
}

Related

Passing params from functional component to class component in React Native

I have functional component where I am passing params at onPress. Below is the code:
PAGE1
const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
};
I was able to pass this to a functional component, where I was getting the data, and was able to update my state in the functional component. Below is the code snippet from functional component:
PAGE2
function gotoSearch() {
navigation.navigate('SearchScreen',{
onPress:(data)=>{
console.log("Location Selected",data);
updateStateVar({
...stateVar,
address_line_1: data.address_line1,
address_line_2: data.address_line2,
area: data.area,
city: data.city,
country: data.country,
pincode: data.postCode,
lat:data.lat,
lng:data.lng,
});
}
});
}
I have another component which is a class component, I tried to do the same thing, but is showed me the error. Below is the code snippet from class component:
PAGE3
gotoSearch = () => {
this.props.navigation.navigate('SearchScreen'), {
onPress: (data) => {
console.log("Location Selected",data);
}
}
}
error:
cannot read property of 'onPress' of undefined
const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
^
};
you will need to export onPress function
export const onPress = (data) => {
navigation.goBack();
route.params.onPress(data);
};
then use it inside your class component
gotoSearch = () => {
this.props.navigation.navigate('SearchScreen'), {
onPress: (data) => {
console.log("Location Selected",data);
}
}
if your function exists in a different file and then first import it and then use
import {onPress} from './file.js'

componentDidUpdate not being called

I have a react native app, and I am calling componentDidUpdate on App.js, but it doesn't fire.
I wonder if this is because I am calling from App.js?
Here is the App.js files:
class App extends Component {
componentDidUpdate = () => {
if (this.props.text && this.props.text.toString().trim()) {
Alert.alert(this.props.title || 'Mensagem', this.props.text.toString());
this.props.clearMessage();
}
}
render() {
return (
<NavigationContainer>
<Navigator />
</NavigationContainer>
)
}
}
const mapStateToProps = ({ message }) => {
return {
title: message.title,
text: message.text
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({
title: '',
text: ''
}))
}
}
const connectDispatch = connect(mapStateToProps, mapDispatchToProps);
const connectApp = connectDispatch(App);
export default connectApp;
And here is where I am calling it.Inside a dispatch in posts action.
.then(res => {
dispatch(fetchPosts());
dispatch(postCreated());
dispatch(setMessage({
title: 'Sucesso',
text: 'Nova Postagem!'
}));
});
All other dispatchs are fired.
It's not the if that is preventing the alert to be fired, because I already put the alert outside of the if.
Change this
componentDidUpdate = () => { ... }
for this:
componentDidUpdate(prevProps, prevState, snapshot) { ... }
Keep in mind the componentDidUpdate does not trigger on first render
Thanks all!
I could fix it.
Instead of importing from '.ActionTypes' I was importing from 'Message'
import { SET_MESSAGE } from '../actions/ActionTypes';
I am new to Redux and it caught me offguard!

React Native: TypeError: this.state.schedule.map is not an object

Hey I am new to React Native and currently I'm trying to put data in a picker using data from API. I'm confused that it got error say TypeError: null is not an object (evaluating this.state.schedules.map). Is there something wrong with the state or is there any concept that I misunderstood
Here is fetch API
export function getSchedule (token, resultCB) {
var endpoint = "/api/getList"
let header = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Bearer " + token
};
return dispatch => {
return fetchAPI(endpoint, 'GET', header)
.then((json) => {
dispatch({ type: t.SCHEDULE, schedules: json.datas.description });
resultCB(json.schedules)
})
.catch((error) => {
dispatch({ type: types.EMPTY_SCHEDULE });
resultCB(error)
})
}
}
this is where i put my picker
export const mapStateToProps = state => ({
token: state.authReducer.token,
message: state.authReducer.message,
schedules: state.authReducer.schedules
});
export const mapDispatchToProps = (dispatch) => ({
actionsAuth: bindActionCreators(authAction, dispatch)
});
class Change extends Component {
constructor(){
super();
this.state={
staffId: "",
schedule: '',
type_absen: 1,
schedules: null
}
}
componentDidMount(){
this.props.actionsAuth.getSchedule(this.props.token);
}
render() {
return (
<View style={styles.picker}>
<Picker
selectedValue={this.state.schedule}
style={{backgroundColor:'white'}}
onValueChange={(sch) => this.setState({schedule: sch})}>
{this.state.schedules.map((l, i) => {
return <Picker.Item value={l} label={i} key={i} /> })}
</Picker>
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Change);
This isn’t a React Native specific error. You initialized schedules to null so on first render, you try to call .map on null. That’s what is causing your error.
You fetch your data correctly in componentDidMount but that lifecycle method will fire after the initial render.
One common way to fix this is to initialize schedules to an empty array.
First initialise schedules: [] in the state with empty array, not with the null.
Fetching data in componentDidMount() is correct. ComponentDidMount() will be called after the first render of component so you have to update the state in the component from the updated store.
you can check whether props is changing or not in componentWillReceiveProps (depreciated) or in the latest alternative of componentWillReceiveProps method that is getDerivedStateFromProps().
Below is the syntax for both
componentWillReceiveProps(nextProps) {
if (this.props.schedules !== nextProps.schedules) {
this.setState({ schedules: nextProps.schedules });
}
}
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.schedules !== prevState.schedules) {
return { schedules: nextProps.schedules };
}
else return null; // Triggers no change in the state
}
Make sure your component should connected to store using connect

React Navigation Warning: "jumpToIndex is not a Function"

I recently updated react-navigation to version 2.18.0 and a section of my code which used to work no longer does. After combing through the documentation, I'm still having trouble reproducing the functionality I had before.
Essentially I wanted all the data that the stats screen needed to be loaded before jumpToIndex is called, so that the StatsScreen Component had access to updated data before render().
This functionality used to work, but now I'm getting an "Unhandled Promise Rejection: TypeError: jumpToIndex is not a function." warning. and jumpToIndex never happened.
In App.js I changed TabNavigator to createBottomTabNavigator, and made the necessary changes for the update.
const RootNavigator = createBottomTabNavigator({
Home: {
screen: HomeStackNavigator,
navigationOptions: ({ navigation }) => ({
//Navigation options here
),
}),
},
StatsScreen: {
screen: StatsScreen,
},
}, {
lazy: false,
});
In StatsScreen.js:
export default class StatsScreen extends Component {
static navigationOptions = ({ navigation }) => ({
tabBarOnPress: async (tab, jumpToIndex) => {
if (!tab.focused) {
await navigation.state.params.update();
jumpToIndex(tab.index);
}
},
});
async componentDidMount() {
this.props.navigation.setParams({
update: this._updateStats.bind(this),
});
}
async _updateStats() {
//logic in this function calls updateData() if needed.
}
async _updateData() {
//Update the data
}
render() {
return (
// Component JSX ommitted from this example
);
}
}
Any ideas on what needs to be done?
I found the solution on Andrei Pfeiffer's blog: https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
Essentially I changed the navigation options to the following code:
static navigationOptions = () => ({
async tabBarOnPress({ navigation, defaultHandler }) {
await navigation.state.params.onTabFocus();
defaultHandler();
},
});
onTabFocus() now does the same work that updateStats() used to do.

Get warning after updating component in Navigator

I have a container in my React Native app and and I use it like preload to show scene Loading... before I get data from server. So I dispatch an action to fetch user data and after that I update my state I try to push new component to Navigator but I've got an error:
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
And I don't understand what is the best way to fix my problem.
So my container:
import myComponent from '../components'
class App extends Component {
componentDidMount() {
this.props.dispatch(fetchUser());
}
_navigate(component, type = 'Normal') {
this.props.navigator.push({
component,
type
})
}
render() {
if (!this.props.isFetching) {
this._navigate(myComponent);
}
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Loading...
</Text>
</View>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
isFetching: React.PropTypes.bool,
user: React.PropTypes.string
};
export default connect((state) => ({
isFetching: state.data.isFetching,
data: state.data.user
}))(App);
My reducer:
const data = (state = initialState, action) => {
switch (action.type) {
case types.USER_FETCH_SUCCEEDED:
return {
...state,
isFetching: false,
user: action.user
};
default:
return state;
}
};
Don't trigger anything that can setState inside the body of your render method. If you need to listen to incoming props, use componentWillReceiveProps
Remove this from render():
if (!this.props.isFetching) {
this._navigate(myComponent);
}
and add componentWillReceiveProps(nextProps)
componentWillReceiveProps(nextProps) {
if (!nextProps.isFetching) {
this._navigate(myComponent);
}
}