i'm still new to react native and i'm trying to make a modal with timer and i got an error say undefined is not an object (evaluating '_this5.state.error[key]') i tried to open the modal with setTimeout(), i think it has the problem with the state, anyone has an idea to fix it? Thanks
here is my code
class FormInput extends Component {
constructor(props) {
super(props);
const { fields, error } = props;
this.state = this.createState(fields, error);
this.state = {
visible: false
}
//bind functions
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
componentDidMount(){
this.timer = setTimeout(this.showModal, 5000); //auto reset after 60 seconds of inactivity
}
componentWillUnmount(){
clearTimeout(this.timer);
}
showModal() {
this.setState ({ visible: true})
}
closeModal() {
this.setState ({ visible: false})
}
createState(fields, error) {
const state = {};
fields.forEach((field) => {
let { key, type, value, mandatory } = field;
state[key] = { type: type, value: value, mandatory: mandatory };
})
state["error"] = error;
state["submitted"] = false;
return state;
}
render() {
return (
<View>
<AlertModal visible={this.showModal} close={this.closeModal}/>
</View>
);
Make showModal and closeModal an arrow function
showModal = () => {
this.setState ({ visible: true })
}
closeModal = () => {
this.setState ({ visible: false })
}
or bind them in constructor.
Also visible prop is a boolean and you are passing a function. Pass this.state.visible to fix the issue.
<AlertModal visible={this.state.visible} close={this.closeModal} />
--- UPDATED ---
So after checking out your updated code, I was able to figure out what was the issue. In constructor you are doing this
this.state = this.createState(fields, error);
this.state = {
visible: false
}
which overrides this.state. So I will suggest you to move visible: false into createState function and remove it from constructor.
declare showModal as a arrow function
showModal = () => {
this.setState ({ visible: true})
}
or bind the context for showModal
this.timer = setTimeout(this.showModal.bind(this), 5000)
or
this.timer = setTimeout(() => {this.showModal()}, 5000)
learn more about javascript context this
Related
I have a react native app and i'm trying to update a Date in a custom context.
ThemeContext
export const ThemeContext = React.createContext({
theme: 'dark',
toggleTheme: () => { },
date: new Date(),
setDate: (date: Date) => { }
});
Basic context with the date and the function to update it
App.tsx
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: 'dark',
date: new Date()
};
}
render() {
const { theme, date } = this.state;
const currentTheme = themes[theme];
const toggleTheme = () => {
const nextTheme = theme === 'light' ? 'dark' : 'light';
this.setState({ theme: nextTheme });
};
const setDate = (date: Date) => {
// ERROR is raised HERE
this.setState({date: date});
}
return (
<>
<IconRegistry icons={EvaIconsPack} />
<ThemeContext.Provider value={{ theme, toggleTheme, date, setDate }}>
...
</ThemeContext.Provider>
</>
);
}
}
I simply hava a state with a Date and create a setDate function. I wrapped my app into the context provider.
Picker
class PickerContent extends React.Component<{}, ContentState> {
static contextType = ThemeContext;
constructor(props: {} | Readonly<{}>) {
super(props);
let currentMode = 'date';
let show = false;
if (Platform.OS === 'ios') {
currentMode = 'datetime';
show = true;
}
this.state = {
date: new Date(),
mode: currentMode,
show: show
};
}
setMode(currentMode: string) {
this.setState({ mode: currentMode, show: true });
};
onChange(_event: any, selectedDate: Date) {
const currentDate = selectedDate || this.state.date;
// I update the context here
this.context.setDate(currentDate);
this.setState({date: currentDate, show: Platform.OS === 'ios'})
}
render() {
const {show, date, mode } = this.state;
const color = 'dark';
return (
<>
...
{show && <DateTimePicker
...
onChange={(event, selectedDate) => this.onChange(event, selectedDate)}>
</DateTimePicker>}</>
)
}
}
I use the lib 'DateTimePicker' to choose a date and bind the onChange to update the context. This picker is on a modal.
So the warning appears when the onChange function of DateTimePicker is trigger. The error is on the setState of App.tsx (in setDate)
Warning: Cannot update during an existing state transition (such as within 'render'). Render methods should be a pure function of props and state.
Would you know how to correct this error?
EDIT :
I was using a Modal component from the UI Kitten library. I switch to the react native Modal and the error is gone. It seems that the error comes from the Library. SORRY
thank you in advance for your help,
Sylvain
If you're using class component, what you can do is to move the context update fn inside componentDidUpdate. You just compare your states, if your state has changed, you update your context with the new value.
Something along the lines of:
componentDidUpdate(prevProps, prevState) {
if (prevState.date !== this.state.date) {
this.context.setDate(this.state.date);
}
}
onChange(_event: any, selectedDate: Date) {
const currentDate = selectedDate || this.state.date;
this.setState({date: currentDate, show: Platform.OS === 'ios'})
}
This way you're not updating your context during render.
If you refactor this component to functional, you would be able to do the same thing using useEffect.
I edited my question. The error finally seems to come from the library used (ui kitten).
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!
I'm fairly new at ReactNative. I have a singleton like this:
export default class Locker extends Component {
static data = Locker.data == null ? new Locker() : this.data;
constructor(props) {
super(props);
this.setState ({ username: "" });
AsyncStorage.getItem('username', (error, result) => {
if (result) { this.setState({ username: result }); }
});
this.getUsername = this.getUsername.bind(this);
this.setUsername = this.setUsername.bind(this);
}
getUsername = () => {
return this.state.username;
}
setUsername = (value) => {
AsyncStorage.setItem('username', value);
this.setState({ username: value });
}
}
And this is my main app:
export default class Home extends Component {
constructor(props) {
super(props);
this.showAlert = this.showAlert.bind(this);
}
showAlert() {
Alert.alert(
'This is an alert',
'Your saved username is ' + Locker.data.getUsername(),
[
{text: 'OK', onPress: console.log("Done")},
],
{cancelable: false},
);
}
render() {
return(
<View style={{ span: 1, backgroundColor: "white" }}>
<Button title="Press me" onPress={ () => this.showAlert() }/>
</View>
);
}
}
When I run the app, I see a button on the screen as expected. When I tap on the button, I get error undefined is not an object (evaluating '_this.state.username').
Why? I read somewhere that this may be because of .bind(this) and arrow function. That's why I add .bind(this) and arrow function everywhere. But still not solve the problem. This problem does not arise if I access state on the Home main class methods.
it is not a good idea to use setState() on the constructor, because react-native will render the component and its children (and this is not a proper thing to do in constructor because the component has not been rendered)
just initiate the state with this.state = {username: ""};
i think it is because of you did not define :
this.state = {
username: ""
}
in the constructor method ,replace it just after super(props)
Use this.state = {username: ""}instead of this.setState ({username: ""}) in constructor.
You don't need bind(this) if you use arrow functions
In your Locker class constructor just replace this code
from
constructor(props) {
super(props);
this.setState ({ username: "" });
AsyncStorage.getItem('username', (error, result) => {
if (result) { this.setState({ username: result }); }
});
this.getUsername = this.getUsername.bind(this);
this.setUsername = this.setUsername.bind(this);
}
to
constructor(props) {
super(props);
this.state={ username: "" };
AsyncStorage.getItem('username', (error, result) => {
if (result) { this.setState({ username: result }); }
});
this.getUsername = this.getUsername.bind(this);
this.setUsername = this.setUsername.bind(this);
}
Just need to replace this.state = { username: "" }
I am trying to use the barcode scanner from react-native-camera. First, off it scans a QR-code and extracts a String, after that it navigates to the next Screen with react-navigation. In the second screen, it makes an API-call.
Now if I go back to the scanner screen, de QR-code will be scanned immediately. That's where I run into an error and the scanner freezes. I usually get this error:
Can't call setState (or forceUpdate) on an unmounted component
I think it's because my componentWillUnmount cleanup doesn't work properly or fast enough, but I already cancel the axios request.
requestCode = (code) => {
if (cancel != undefined) {
cancel();
}
axios.get(API_URI + code, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).then(response => {
console.log(response)
//checks if code was already called
this.checkUsed(response.data)
})
.catch(error => {
this.setState({ isValid: false })
});
}
componentWillUnmount() {
cancel();
}
Maybe I could mount the camera-scanner a little bit later so it doesn't scan this fast or is it maybe even an error with React Navigation?
You can use a flag to control.
class QR extends Component {
constructor(props) {
super(props)
this.state = {
scanable: true
}
this.cameraAttrs = {
ref: ref => {
this.camera = ref
},
style: styles.preview,
type: RNCamera.Constants.Type.back,
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
onBarCodeRead: ({ data }) => {
this.callback(data)
}
}
}
componentWillMount() {
this._mounted = true
}
componentWillUnmount() {
this._mounted = false
}
callback(text) {
if (!this.state.scanable) {
return
}
console.log(text)
this.setState({ scanable: false })
setTimeout(() => {
if (this._mounted) {
this.setState({ scanable: true })
}
}, 1000) // 1s cooldown
}
render() {
return (
<View style={styles.container}>
<RNCamera
{...this.cameraAttrs}
>
</RNCamera>
</View>
)
}
}
I need to hide a hamburger-menu/location icon on the toolbar while the login screen is active. One option I thought would work is to have the icons set to a empty string by default. And use the EventEmitter in the success callback function in my Login.js & Logout.js, and then listen for it in my toolbar component. Sending a bool to determine show/hide. I am not sure if there is a better way of doing this so I'm up for suggestions. The Emit/Listen events work as expected. The issue is how I use a variable to apply the empty string or named icon.
here is the Toolbar Component.
export default class Toolbar extends Component {
//noinspection JSUnusedGlobalSymbols
static contextTypes = {
navigator: PropTypes.object
};
//noinspection JSUnusedGlobalSymbols
static propTypes = {
onIconPress: PropTypes.func.isRequired
};
//noinspection JSUnusedGlobalSymbols
constructor(props) {
super(props);
this.state = {
title: AppStore.getState().routeName,
theme: AppStore.getState().theme,
menuIcon: '',
locationIcon: ''
};
}
emitChangeMarket() {
AppEventEmitter.emit('onClickEnableNavigation');
}
//noinspection JSUnusedGlobalSymbols
componentDidMount = () => {
AppStore.listen(this.handleAppStore);
AppEventEmitter.addListener('showIcons', this.showIcons.bind(this));
};
//noinspection JSUnusedGlobalSymbols
componentWillUnmount() {
AppStore.unlisten(this.handleAppStore);
}
handleAppStore = (store) => {
this.setState({
title: store.routeName,
theme: store.theme
});
};
showIcons(val) {
if (val === true) {
this.setState({
menuIcon: 'menu',
locationIcon: 'location-on'
});
} else {
this.setState({
menuIcon: '',
locationIcon: ''
});
}
}
render() {
let menuIcon = this.state.menuIcon;
let locationIcon = this.state.locationIcon;
const {navigator} = this.context;
const {theme} = this.state;
const {onIconPress} = this.props;
return (
<MaterialToolbar
title={navigator && navigator.currentRoute ? navigator.currentRoute.title : 'Metro Tracker Login'}
primary={theme}
icon={navigator && navigator.isChild ? 'keyboard-backspace' : {menuIcon}}
onIconPress={() => navigator && navigator.isChild ? navigator.back() : onIconPress()}
actions={[{
icon: {locationIcon},
onPress: this.emitChangeMarket.bind(this)
}]}
rightIconStyle={{
margin: 10
}}
/>
);
}
}
The warning message I get is the:
Invalid prop icon of type object supplied to toolbar, expected a string.
how can I pass a string while wrapped in variable brackets?
Or if easier how can I hide/show the entire toolbar? either way works.
Try removing the brackets around menuIcon and locationIcon:
...
icon={navigator && navigator.isChild ? 'keyboard-backspace' : menuIcon}
...
icon: locationIcon,
...